From 13ec3b1584a83ce7e7a649e2b500aaf4fe4bbd8a Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 11 Dec 2024 21:35:56 +0800 Subject: [PATCH] remove explorer files --- .../TableEditorLayout/TableEditorMenu.tsx | 2 +- .../TableMenuEmptyState.tsx | 0 .../layouts/editors/editor-base-layout.tsx | 2 +- .../layouts/explorer/EntityListItem.tsx | 449 ----------- .../layouts/explorer/SQLEditorMenu.tsx | 153 ---- .../explorer/SQLEditorNavV2/SQLEditorNav.tsx | 729 ------------------ .../SQLEditorNavV2/SQLEditorNav.utils.ts | 54 -- .../SQLEditorNavV2/SQLEditorTreeViewItem.tsx | 298 ------- .../layouts/explorer/TableEditorMenu.tsx | 326 -------- .../layouts/explorer/empty-state-panel.tsx | 78 -- .../components/layouts/explorer/layout.tsx | 90 --- .../layouts/explorer/schema-button.tsx | 27 - .../components/layouts/tabs/explorer-tabs.tsx | 6 +- .../components/layouts/tabs/new-tab.tsx | 33 +- apps/studio/components/tabs/TabIcon.tsx | 61 -- 15 files changed, 5 insertions(+), 2303 deletions(-) rename apps/studio/components/layouts/{explorer => TableEditorLayout}/TableMenuEmptyState.tsx (100%) delete mode 100644 apps/studio/components/layouts/explorer/EntityListItem.tsx delete mode 100644 apps/studio/components/layouts/explorer/SQLEditorMenu.tsx delete mode 100644 apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.tsx delete mode 100644 apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.utils.ts delete mode 100644 apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorTreeViewItem.tsx delete mode 100644 apps/studio/components/layouts/explorer/TableEditorMenu.tsx delete mode 100644 apps/studio/components/layouts/explorer/empty-state-panel.tsx delete mode 100644 apps/studio/components/layouts/explorer/layout.tsx delete mode 100644 apps/studio/components/layouts/explorer/schema-button.tsx delete mode 100644 apps/studio/components/tabs/TabIcon.tsx diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 7ed685a5c72..5225eb89e0d 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -40,7 +40,7 @@ import { } from 'ui-patterns/InnerSideMenu' import { useProjectContext } from '../ProjectLayout/ProjectContext' import EntityListItem from './EntityListItem' -import { TableMenuEmptyState } from '../explorer/TableMenuEmptyState' +import { TableMenuEmptyState } from './TableMenuEmptyState' import { SkeletonMenuList } from 'components/editor-menu-list-skeleton' const TableEditorMenu = () => { diff --git a/apps/studio/components/layouts/explorer/TableMenuEmptyState.tsx b/apps/studio/components/layouts/TableEditorLayout/TableMenuEmptyState.tsx similarity index 100% rename from apps/studio/components/layouts/explorer/TableMenuEmptyState.tsx rename to apps/studio/components/layouts/TableEditorLayout/TableMenuEmptyState.tsx diff --git a/apps/studio/components/layouts/editors/editor-base-layout.tsx b/apps/studio/components/layouts/editors/editor-base-layout.tsx index ba6fc5a90d7..3324211e181 100644 --- a/apps/studio/components/layouts/editors/editor-base-layout.tsx +++ b/apps/studio/components/layouts/editors/editor-base-layout.tsx @@ -40,7 +40,7 @@ export const EditorBaseLayout = ({ children, ...props }: ExplorerLayoutProps) => )} > {hideTabs && } - {!hideTabs && } + {!hideTabs && } ) : null}
{children}
diff --git a/apps/studio/components/layouts/explorer/EntityListItem.tsx b/apps/studio/components/layouts/explorer/EntityListItem.tsx deleted file mode 100644 index c0c06b8dd14..00000000000 --- a/apps/studio/components/layouts/explorer/EntityListItem.tsx +++ /dev/null @@ -1,449 +0,0 @@ -import * as Tooltip from '@radix-ui/react-tooltip' -import saveAs from 'file-saver' -import { - Copy, - Download, - Edit, - Eye, - Lock, - MoreHorizontal, - Table2, - Trash, - Unlock, -} from 'lucide-react' -import Link from 'next/link' -import Papa from 'papaparse' -import { toast } from 'sonner' - -import { IS_PLATFORM } from 'common' -import { - MAX_EXPORT_ROW_COUNT, - MAX_EXPORT_ROW_COUNT_MESSAGE, -} from 'components/grid/components/header/Header' -import { parseSupaTable } from 'components/grid/SupabaseGrid.utils' -import { Markdown } from 'components/interfaces/Markdown' -import { - formatTableRowsToSQL, - getEntityLintDetails, -} from 'components/interfaces/TableGridEditor/TableEntity.utils' -import type { ItemRenderer } from 'components/ui/InfiniteList' -import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' -import { Entity } from 'data/entity-types/entity-types-infinite-query' -import { useProjectLintsQuery } from 'data/lint/lint-query' -import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id' -import { getTableEditor } from 'data/table-editor/table-editor-query' -import { isTableLike } from 'data/table-editor/table-editor-types' -import { fetchAllTableRows } from 'data/table-rows/table-rows-query' -import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' -import { useTableEditorStateSnapshot } from 'state/table-editor' -import { - cn, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger, - TreeViewItemVariant, -} from 'ui' -import { useProjectContext } from '../ProjectLayout/ProjectContext' -import { getTabsStore } from 'state/tabs' -import { useSnapshot } from 'valtio' - -export interface EntityListItemProps { - id: number | string - projectRef: string - isLocked: boolean - isActive?: boolean -} - -const EntityListItem: ItemRenderer = ({ - id, - projectRef, - item: entity, - isLocked, - isActive: _isActive, -}) => { - const { project } = useProjectContext() - const snap = useTableEditorStateSnapshot() - const { selectedSchema } = useQuerySchemaState() - - const isActive = _isActive ?? Number(id) === entity.id - - const { data: lints = [] } = useProjectLintsQuery({ - projectRef: project?.ref, - }) - - const tableHasLints: boolean = getEntityLintDetails( - entity.name, - 'rls_disabled_in_public', - ['ERROR'], - lints, - selectedSchema - ).hasLint - - const viewHasLints: boolean = getEntityLintDetails( - entity.name, - 'security_definer_view', - ['ERROR', 'WARN'], - lints, - selectedSchema - ).hasLint - - const materializedViewHasLints: boolean = getEntityLintDetails( - entity.name, - 'materialized_view_in_api', - ['ERROR', 'WARN'], - lints, - selectedSchema - ).hasLint - - const formatTooltipText = (entityType: string) => { - return Object.entries(ENTITY_TYPE) - .find(([, value]) => value === entityType)?.[0] - ?.toLowerCase() - ?.split('_') - ?.join(' ') - } - - const exportTableAsCSV = async () => { - if (IS_PLATFORM && !project?.connectionString) { - return console.error('Connection string is required') - } - const toastId = toast.loading(`Exporting ${entity.name} as CSV...`) - - try { - const table = await getTableEditor({ - id: entity.id, - projectRef, - connectionString: project?.connectionString, - }) - if (isTableLike(table) && table.live_rows_estimate > MAX_EXPORT_ROW_COUNT) { - return toast.error( - , - { id: toastId } - ) - } - - const supaTable = table && parseSupaTable(table) - - if (!supaTable) { - return toast.error(`Failed to export table: ${entity.name}`, { id: toastId }) - } - - const rows = await fetchAllTableRows({ - projectRef, - connectionString: project?.connectionString, - table: supaTable, - }) - const formattedRows = rows.map((row) => { - const formattedRow = row - Object.keys(row).map((column) => { - if (typeof row[column] === 'object' && row[column] !== null) - formattedRow[column] = JSON.stringify(formattedRow[column]) - }) - return formattedRow - }) - - if (formattedRows.length > 0) { - const csv = Papa.unparse(formattedRows, { - columns: supaTable.columns.map((column) => column.name), - }) - const csvData = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) - saveAs(csvData, `${entity!.name}_rows.csv`) - } - - toast.success(`Successfully exported ${entity.name} as CSV`, { id: toastId }) - } catch (error: any) { - toast.error(`Failed to export table: ${error.message}`, { id: toastId }) - } - } - - const exportTableAsSQL = async () => { - if (IS_PLATFORM && !project?.connectionString) { - return console.error('Connection string is required') - } - const toastId = toast.loading(`Exporting ${entity.name} as SQL...`) - - try { - const table = await getTableEditor({ - id: entity.id, - projectRef, - connectionString: project?.connectionString, - }) - - if (isTableLike(table) && table.live_rows_estimate > MAX_EXPORT_ROW_COUNT) { - return toast.error( - , - { id: toastId } - ) - } - - const supaTable = table && parseSupaTable(table) - - if (!supaTable) { - return toast.error(`Failed to export table: ${entity.name}`, { id: toastId }) - } - - const rows = await fetchAllTableRows({ - projectRef, - connectionString: project?.connectionString, - table: supaTable, - }) - const formattedRows = rows.map((row) => { - const formattedRow = row - Object.keys(row).map((column) => { - if (typeof row[column] === 'object' && row[column] !== null) - formattedRow[column] = JSON.stringify(formattedRow[column]) - }) - return formattedRow - }) - - if (formattedRows.length > 0) { - const sqlStatements = formatTableRowsToSQL(supaTable, formattedRows) - const sqlData = new Blob([sqlStatements], { type: 'text/sql;charset=utf-8;' }) - saveAs(sqlData, `${entity!.name}_rows.sql`) - } - - toast.success(`Successfully exported ${entity.name} as SQL`, { id: toastId }) - } catch (error: any) { - toast.error(`Failed to export table: ${error.message}`, { id: toastId }) - } - } - - const EntityTooltipTrigger = ({ entity }: { entity: Entity }) => { - let tooltipContent = null - - switch (entity.type) { - case ENTITY_TYPE.TABLE: - if (tableHasLints) { - tooltipContent = 'RLS Disabled' - } - break - case ENTITY_TYPE.VIEW: - if (viewHasLints) { - tooltipContent = 'Security Definer view' - } - break - case ENTITY_TYPE.MATERIALIZED_VIEW: - if (materializedViewHasLints) { - tooltipContent = 'Security Definer view' - } - - break - case ENTITY_TYPE.FOREIGN_TABLE: - tooltipContent = 'RLS is not enforced on foreign tables' - - break - default: - break - } - - if (tooltipContent) { - return ( - - - - - - - - {tooltipContent} - - - - ) - } - - return null - } - - const tabsStore = getTabsStore(projectRef) - const tabs = useSnapshot(tabsStore) - - const isOpened = Object.values(tabs.tabsMap).some((tab) => tab.metadata?.tableId === entity.id) - - return ( - - <> - {isActive &&
} - - - {entity.type === ENTITY_TYPE.TABLE ? ( - - ) : entity.type === ENTITY_TYPE.VIEW ? ( - - ) : ( -
- {Object.entries(ENTITY_TYPE) - .find(([, value]) => value === entity.type)?.[0]?.[0] - ?.toUpperCase()} -
- )} -
- - - - {formatTooltipText(entity.type)} - - -
-
- - {entity.name} - - -
- - {entity.type === ENTITY_TYPE.TABLE && isActive && !isLocked && ( - - - - - - { - e.stopPropagation() - snap.onEditTable() - }} - > - - Edit Table - - { - e.stopPropagation() - snap.onDuplicateTable() - }} - > - - Duplicate Table - - - - - View Policies - - - - - - - Export Data - - - { - e.stopPropagation() - exportTableAsCSV() - }} - > - Export table as CSV - - { - e.stopPropagation() - exportTableAsSQL() - }} - > - Export table as SQL - - - - - - { - e.stopPropagation() - snap.onDeleteTable() - }} - > - - Delete Table - - - - )} - - - ) -} - -export default EntityListItem diff --git a/apps/studio/components/layouts/explorer/SQLEditorMenu.tsx b/apps/studio/components/layouts/explorer/SQLEditorMenu.tsx deleted file mode 100644 index 7177bc20696..00000000000 --- a/apps/studio/components/layouts/explorer/SQLEditorMenu.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { Edit2, FilePlus, FolderPlus, Plus } from 'lucide-react' -import { useRouter } from 'next/router' -import { useState } from 'react' -import { toast } from 'sonner' - -import { useParams } from 'common' -import { untitledSnippetTitle } from 'components/interfaces/SQLEditor/SQLEditor.constants' -import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLEditor.utils' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useSelectedProject } from 'hooks/misc/useSelectedProject' -import { uuidv4 } from 'lib/helpers' -import { useProfile } from 'lib/profile' -import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from 'ui' -import { - InnerSideBarFilterSearchInput, - InnerSideBarFilterSortDropdown, - InnerSideBarFilterSortDropdownItem, - InnerSideBarFilters, - InnerSideMenuItem, -} from 'ui-patterns/InnerSideMenu' -import { SQLEditorNav as SQLEditorNavV2 } from './SQLEditorNavV2/SQLEditorNav' - -interface SQLEditorMenuProps { - onViewOngoingQueries: () => void -} - -export const SQLEditorMenu = ({ onViewOngoingQueries }: SQLEditorMenuProps) => { - const router = useRouter() - const { profile } = useProfile() - const project = useSelectedProject() - const { ref } = useParams() - - const snapV2 = useSqlEditorV2StateSnapshot() - const [searchText, setSearchText] = useState('') - - const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', { - resource: { type: 'sql', owner_id: profile?.id }, - subject: { id: profile?.id }, - }) - - const createNewFolder = () => { - // [Joshen] LEFT OFF: We need to figure out a good UX for creating folders - // - Modal? Directly chuck into the tree view like storage explorer? - if (!ref) return console.error('Project ref is required') - snapV2.addNewFolder({ projectRef: ref }) - // createFolder({ projectRef: ref, name: 'test' }) - } - - const handleNewQuery = async () => { - if (!ref) return console.error('Project ref is required') - if (!project) return console.error('Project is required') - if (!profile) return console.error('Profile is required') - if (!canCreateSQLSnippet) { - return toast('Your queries will not be saved as you do not have sufficient permissions') - } - - try { - const snippet = createSqlSnippetSkeletonV2({ - id: uuidv4(), - name: untitledSnippetTitle, - owner_id: profile.id, - project_id: project.id, - sql: '', - }) - snapV2.addSnippet({ projectRef: ref, snippet }) - router.push(`/project/${ref}/sql/${snippet.id}`) - setSearchText('') - } catch (error: any) { - toast.error(`Failed to create new query: ${error.message}`) - } - } - - return ( -
-
-
-

Workbook

- - -
-
- - setSearchText(e.target.value)} - > - snapV2.setOrder(value)} - > - - Alphabetical - - - Created At - - - - -
- - {/*
- - Templates - - - Quickstarts - -
*/} - - -
- - {/*
- -
*/} -
- ) -} diff --git a/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.tsx b/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.tsx deleted file mode 100644 index d57c8e12415..00000000000 --- a/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.tsx +++ /dev/null @@ -1,729 +0,0 @@ -import { Eye, EyeOffIcon, Heart, Pointer, Unlock } from 'lucide-react' -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' -import { toast } from 'sonner' - -import { useParams } from 'common' -import DownloadSnippetModal from 'components/interfaces/SQLEditor/DownloadSnippetModal' -import { MoveQueryModal } from 'components/interfaces/SQLEditor/MoveQueryModal' -import RenameQueryModal from 'components/interfaces/SQLEditor/RenameQueryModal' -import { untitledSnippetTitle } from 'components/interfaces/SQLEditor/SQLEditor.constants' -import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLEditor.utils' -import { useContentCountQuery } from 'data/content/content-count-query' -import { useContentDeleteMutation } from 'data/content/content-delete-mutation' -import { getContentById } from 'data/content/content-id-query' -import { useSQLSnippetFoldersDeleteMutation } from 'data/content/sql-folders-delete-mutation' -import { - Snippet, - SnippetDetail, - SnippetFolder, - getSQLSnippetFolders, - useSQLSnippetFoldersQuery, -} from 'data/content/sql-folders-query' -import { useSqlSnippetsQuery } from 'data/content/sql-snippets-query' -import { useLocalStorage } from 'hooks/misc/useLocalStorage' -import { useSelectedProject } from 'hooks/misc/useSelectedProject' -import { useProfile } from 'lib/profile' -import uuidv4 from 'lib/uuid' -import { - useFavoriteSnippets, - useSnippetFolders, - useSnippets, - useSqlEditorV2StateSnapshot, -} from 'state/sql-editor-v2' -import { Separator, Skeleton, TreeView, TreeViewItem } from 'ui' -import { - InnerSideBarEmptyPanel, - InnerSideMenuCollapsible, - InnerSideMenuCollapsibleContent, - InnerSideMenuCollapsibleTrigger, - InnerSideMenuSeparator, -} from 'ui-patterns' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' -import { ROOT_NODE, formatFolderResponseForTreeView } from './SQLEditorNav.utils' -import { SQLEditorTreeViewItem } from './SQLEditorTreeViewItem' -import { getTabsStore } from 'state/tabs' - -interface SQLEditorNavProps { - searchText: string -} - -export const SQLEditorNav = ({ searchText: _searchText }: SQLEditorNavProps) => { - const searchText = _searchText.trim() - const router = useRouter() - const { profile } = useProfile() - const project = useSelectedProject() - const { ref: projectRef, id } = useParams() - const snapV2 = useSqlEditorV2StateSnapshot() - const [sort] = useLocalStorage<'name' | 'inserted_at'>('sql-editor-sort', 'inserted_at') - - const [mountedId, setMountedId] = useState(false) - const [showMoveModal, setShowMoveModal] = useState(false) - const [showDeleteModal, setShowDeleteModal] = useState(false) - const [showRenameModal, setShowRenameModal] = useState(false) - const [showFavouriteSnippets, setShowFavouriteSnippets] = useState(false) - const [showSharedSnippets, setShowSharedSnippets] = useState(false) - const [showPrivateSnippets, setShowPrivateSnippets] = useState(true) - - const [defaultExpandedFolderIds, setDefaultExpandedFolderIds] = useState() - const [selectedSnippets, setSelectedSnippets] = useState([]) - const [selectedSnippetToShare, setSelectedSnippetToShare] = useState() - const [selectedSnippetToUnshare, setSelectedSnippetToUnshare] = useState() - const [selectedSnippetToRename, setSelectedSnippetToRename] = useState() - const [selectedSnippetToDownload, setSelectedSnippetToDownload] = useState() - const [selectedFolderToDelete, setSelectedFolderToDelete] = useState() - - // ======================================================= - // [Joshen] Set up favorites, shared, and private snippets - // ======================================================= - const snippets = useSnippets(projectRef as string) - const folders = useSnippetFolders(projectRef as string) - const contents = snippets.filter((x) => - searchText.length > 0 ? x.name.toLowerCase().includes(searchText.toLowerCase()) : true - ) - const snippet = snapV2.snippets[id as string]?.snippet - - const privateSnippets = contents.filter((snippet) => snippet.visibility === 'user') - const numPrivateSnippets = snapV2.privateSnippetCount[projectRef as string] - const privateSnippetsTreeState = - folders.length === 0 && snippets.length === 0 - ? [ROOT_NODE] - : formatFolderResponseForTreeView({ folders, contents: privateSnippets }) - - const favoriteSnippets = useFavoriteSnippets(projectRef as string).filter((x) => - searchText.length > 0 ? x.name.toLowerCase().includes(searchText.toLowerCase()) : true - ) - const numFavoriteSnippets = favoriteSnippets.length - const favoritesTreeState = - numFavoriteSnippets === 0 - ? [ROOT_NODE] - : formatFolderResponseForTreeView({ contents: favoriteSnippets as any }) - - const projectSnippets = contents.filter((snippet) => snippet.visibility === 'project') - const numProjectSnippets = projectSnippets.length - const projectSnippetsTreeState = - numProjectSnippets === 0 - ? [ROOT_NODE] - : formatFolderResponseForTreeView({ contents: projectSnippets }) - - // ================================= - // [Joshen] React Queries - // ================================= - - useSQLSnippetFoldersQuery( - { projectRef }, - { - refetchOnWindowFocus: false, - staleTime: 5 * 60 * 1000, // 5 minutes - onSuccess: (data) => { - if (projectRef !== undefined) { - snapV2.initializeRemoteSnippets({ projectRef, data, sort }) - } - }, - } - ) - - useSqlSnippetsQuery(projectRef, { - onSuccess(data) { - if (projectRef !== undefined) { - const favoriteSnippets = data.snippets.filter((snippet) => snippet.content.favorite) - snapV2.initializeFavoriteSnippets({ projectRef, snippets: favoriteSnippets }) - } - }, - }) - - useContentCountQuery( - { projectRef, type: 'sql' }, - { - onSuccess(data) { - if (projectRef !== undefined) { - snapV2.setPrivateSnippetCount({ projectRef, value: data.count }) - } - }, - } - ) - - const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({ - onError: (error, data) => { - if (error.message.includes('Contents not found')) { - postDeleteCleanup(data.ids) - } else { - toast.error(`Failed to delete query: ${error.message}`) - } - }, - }) - - const { mutate: deleteFolder, isLoading: isDeletingFolder } = useSQLSnippetFoldersDeleteMutation({ - onSuccess: (_, vars) => { - toast.success('Successfully deleted folder') - const { ids } = vars - snapV2.removeFolder(ids[0]) - setSelectedFolderToDelete(undefined) - }, - }) - - // ================================= - // [Joshen] UI functions - // ================================= - - const postDeleteCleanup = (ids: string[]) => { - setShowDeleteModal(false) - setSelectedSnippets([]) - const existingSnippetIds = Object.keys(snapV2.snippets).filter((x) => !ids.includes(x)) - - if (existingSnippetIds.length === 0) { - router.push(`/project/${projectRef}/sql`) - } else if (ids.includes(id as string)) { - router.push(`/project/${projectRef}/sql/${existingSnippetIds[0]}`) - } - - if (ids.length > 0) ids.forEach((id) => snapV2.removeSnippet(id)) - } - - const onConfirmDelete = () => { - if (!projectRef) return console.error('Project ref is required') - deleteContent( - { projectRef, ids: selectedSnippets.map((x) => x.id) }, - { - onSuccess: (data) => { - toast.success( - `Successfully deleted ${selectedSnippets.length.toLocaleString()} quer${selectedSnippets.length > 1 ? 'ies' : 'y'}` - ) - postDeleteCleanup(data) - }, - } - ) - } - - const onConfirmShare = () => { - if (!selectedSnippetToShare) return console.error('Snippet ID is required') - snapV2.shareSnippet(selectedSnippetToShare.id, 'project') - setSelectedSnippetToShare(undefined) - setShowSharedSnippets(true) - - if (projectRef !== undefined) { - snapV2.setPrivateSnippetCount({ - projectRef, - value: snapV2.privateSnippetCount[projectRef] - 1, - }) - } - } - - const onConfirmUnshare = () => { - if (!selectedSnippetToUnshare) return console.error('Snippet ID is required') - snapV2.shareSnippet(selectedSnippetToUnshare.id, 'user') - setSelectedSnippetToUnshare(undefined) - setShowPrivateSnippets(true) - - if (projectRef !== undefined) { - snapV2.setPrivateSnippetCount({ - projectRef, - value: snapV2.privateSnippetCount[projectRef] + 1, - }) - } - } - - const onSelectCopyPersonal = async (snippet: Snippet) => { - if (!profile) return console.error('Profile is required') - if (!project) return console.error('Project is required') - if (!projectRef) return console.error('Project ref is required') - if (!id) return console.error('Snippet ID is required') - - let sql: string = '' - if (!('content' in snippet)) { - // Fetch the content first - const { content } = await getContentById({ projectRef, id: snippet.id }) - sql = content.sql - } else { - sql = (snippet as SnippetDetail).content.sql - } - - const snippetCopy = createSqlSnippetSkeletonV2({ - id: uuidv4(), - name: snippet.name, - sql, - owner_id: profile?.id, - project_id: project?.id, - }) - - snapV2.addSnippet({ projectRef, snippet: snippetCopy }) - snapV2.addNeedsSaving(snippetCopy.id!) - router.push(`/project/${projectRef}/sql/${snippetCopy.id}`) - } - - const onConfirmDeleteFolder = async () => { - if (!projectRef) return console.error('Project ref is required') - if (selectedFolderToDelete === undefined) return console.error('No folder is selected') - - const folderSnippets = contents.filter( - (content) => content.folder_id === selectedFolderToDelete.id - ) - if (folderSnippets.length > 0) { - const ids = folderSnippets.map((x) => x.id) - deleteContent( - { projectRef, ids }, - { - onSuccess: () => { - ids.forEach((id) => snapV2.removeSnippet(id)) - postDeleteCleanup(ids) - deleteFolder({ projectRef, ids: [selectedFolderToDelete?.id] }) - }, - } - ) - } else { - deleteFolder({ projectRef, ids: [selectedFolderToDelete?.id] }) - } - } - - // [Joshen] Just FYI doing a controlled state instead of letting the TreeView component doing it because - // 1. There seems to be no way of accessing the internal state of the TreeView to retrieve the selected nodes - // 2. The component itself doesn't handle UUID for node IDs well - trying to multi select doesn't select the expected nodes - // We're only supporting shift clicks (not cmd/control click) - this is even with the react tree view component itself - const onMultiSelect = (selectedId: string) => { - // The base is always the current query thats selected - const contentIds = contents.map((x) => x.id) - const baseIndex = contentIds.indexOf(id as string) - const targetIndex = contentIds.indexOf(selectedId) - - const floor = Math.min(baseIndex, targetIndex) - const ceiling = Math.max(baseIndex, targetIndex) - - const _selectedSnippets = [] - const sameFolder = contents[floor].folder_id === contents[ceiling].folder_id - - for (let i = floor; i <= ceiling; i++) { - if (sameFolder) { - if (contents[i].folder_id === contents[floor].folder_id) _selectedSnippets.push(contents[i]) - } else { - // [Joshen] Temp don't allow selecting across folders for now - // _selectedSnippets.push(contents[i]) - } - } - - setSelectedSnippets(_selectedSnippets) - } - - // ====================================== - // [Joshen] useEffects kept at the bottom - // ====================================== - - useEffect(() => { - const loadFolderContents = async (folderId: string) => { - const { contents } = await getSQLSnippetFolders({ projectRef, folderId }) - if (projectRef) { - contents?.forEach((snippet) => snapV2.addSnippet({ projectRef, snippet })) - } - } - - if (snippet !== undefined && !mountedId) { - if (snippet.visibility === 'project') setShowSharedSnippets(true) - if (snippet.folder_id) { - setDefaultExpandedFolderIds([snippet.folder_id]) - loadFolderContents(snippet.folder_id) - } - - // Only want to run this once when loading sql/[id] route - setMountedId(true) - } - }, [snippet, mountedId]) - - useEffect(() => { - // Unselect all snippets whenever opening another snippet - setSelectedSnippets([]) - }, [id]) - - return ( - <> - {((numProjectSnippets === 0 && searchText.length === 0) || numProjectSnippets > 0) && ( - <> - - 0 ? ` (${numProjectSnippets})` : ''}`} - /> - - {numProjectSnippets === 0 ? ( - - ) : ( - { - const tabsStore = getTabsStore(projectRef) - const isOpened = Object.values(tabsStore.tabsMap).some( - (tab) => tab.metadata?.sqlId === element.id || tab.id === element.id - ) - - return ( - { - setShowDeleteModal(true) - setSelectedSnippets([element.metadata as unknown as Snippet]) - }} - onSelectRename={() => { - setShowRenameModal(true) - setSelectedSnippetToRename(element.metadata as Snippet) - }} - onSelectDownload={() => { - setSelectedSnippetToDownload(element.metadata as Snippet) - }} - onSelectCopyPersonal={() => { - onSelectCopyPersonal(element.metadata as Snippet) - }} - onSelectUnshare={() => { - setSelectedSnippetToUnshare(element.metadata as Snippet) - }} - /> - ) - }} - /> - )} - - - - )} - - {((numFavoriteSnippets === 0 && searchText.length === 0) || numFavoriteSnippets > 0) && ( - <> - - 0 ? ` (${numFavoriteSnippets})` : ''}`} - /> - - {numFavoriteSnippets === 0 ? ( - - Save a query to favorites for easy accessbility by clicking the{' '} - {' '} - icon. - - } - /> - ) : ( - { - const tabsStore = getTabsStore(projectRef) - const isOpened = Object.values(tabsStore.tabsMap).some( - (tab) => tab.metadata?.sqlId === element.id || tab.id === element.id - ) - - return ( - { - setShowDeleteModal(true) - setSelectedSnippets([element.metadata as unknown as Snippet]) - }} - onSelectRename={() => { - setShowRenameModal(true) - setSelectedSnippetToRename(element.metadata as Snippet) - }} - onSelectDownload={() => { - setSelectedSnippetToDownload(element.metadata as Snippet) - }} - onSelectCopyPersonal={() => { - onSelectCopyPersonal(element.metadata as Snippet) - }} - onSelectShare={() => setSelectedSnippetToShare(element.metadata as Snippet)} - onSelectUnshare={() => { - setSelectedSnippetToUnshare(element.metadata as Snippet) - }} - /> - ) - }} - /> - )} - - - - )} - - - 0 ? ` (${numPrivateSnippets})` : ''}`} - /> - - {!snapV2.loaded[projectRef as string] ? ( - <> -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - ) : folders.length === 0 && numPrivateSnippets === 0 ? ( - -
-
-
-
-
-
-
- -
- {[...Array(4)].map((_, i) => ( -
- -
- ))} -
-
- - ) : ( - x.id)} - aria-label="private-snippets" - expandedIds={defaultExpandedFolderIds} - nodeRenderer={({ element, ...props }) => { - const tabsStore = getTabsStore(projectRef) - const isOpened = Object.values(tabsStore.tabsMap).some( - (tab) => tab.metadata?.sqlId === element.id || tab.id === element.id - ) - - return ( - 1} - status={props.isBranch ? snapV2.folders[element.id].status : 'idle'} - onMultiSelect={onMultiSelect} - onSelectCreate={() => { - if (profile && project) { - const snippet = createSqlSnippetSkeletonV2({ - id: uuidv4(), - name: untitledSnippetTitle, - owner_id: profile?.id, - project_id: project?.id, - folder_id: element.id as string, - sql: '', - }) - snapV2.addSnippet({ projectRef: project.ref, snippet }) - router.push(`/project/${projectRef}/sql/${snippet.id}`) - } - }} - onSelectDelete={() => { - if (props.isBranch) { - setSelectedFolderToDelete(element.metadata as SnippetFolder) - } else { - setShowDeleteModal(true) - if (selectedSnippets.length === 0) { - setSelectedSnippets([element.metadata as unknown as Snippet]) - } - } - }} - onSelectRename={() => { - if (props.isBranch) { - snapV2.editFolder(element.id as string) - } else { - setShowRenameModal(true) - setSelectedSnippetToRename(element.metadata as Snippet) - } - }} - onSelectMove={() => { - setShowMoveModal(true) - if (selectedSnippets.length === 0) { - setSelectedSnippets([element.metadata as Snippet]) - } - }} - onSelectDownload={() => - setSelectedSnippetToDownload(element.metadata as Snippet) - } - onSelectShare={() => setSelectedSnippetToShare(element.metadata as Snippet)} - onEditSave={(name: string) => { - // [Joshen] Inline editing only for folders for now - if (name.length === 0 && element.id === 'new-folder') { - snapV2.removeFolder(element.id as string) - } else if (name.length > 0) { - snapV2.saveFolder({ id: element.id as string, name }) - } - }} - /> - ) - }} - /> - )} - - - - - - setShowRenameModal(false)} - onComplete={() => setShowRenameModal(false)} - /> - - { - setShowMoveModal(false) - setSelectedSnippets([]) - }} - /> - - setSelectedSnippetToDownload(undefined)} - /> - - setSelectedSnippetToShare(undefined)} - onConfirm={onConfirmShare} - alert={{ - title: 'This SQL query will become public to all team members', - description: 'Anyone with access to the project can view it', - }} - > -
    -
  • - - Project members will have read-only access to this query. -
  • -
  • - - Anyone will be able to duplicate it to their personal snippets. -
  • -
-
- - setSelectedSnippetToUnshare(undefined)} - onConfirm={onConfirmUnshare} - alert={{ - title: 'This SQL query will no longer be public to all team members', - description: 'Only you will have access to this query', - }} - > -
    -
  • - - Project members will no longer be able to view this query. -
  • -
-
- - 1 ? 'ies' : 'y'}`}`} - confirmLabel={`Delete ${selectedSnippets.length.toLocaleString()} quer${selectedSnippets.length > 1 ? 'ies' : 'y'}`} - confirmLabelLoading="Deleting query" - loading={isDeleting} - visible={showDeleteModal} - variant="destructive" - onCancel={() => { - setShowDeleteModal(false) - setSelectedSnippets([]) - }} - onConfirm={onConfirmDelete} - alert={ - (selectedSnippets[0]?.visibility as unknown as string) === 'project' - ? { - title: 'This SQL snippet will be lost forever', - description: - 'Deleting this query will remove it for all members of the project team.', - } - : undefined - } - > -

- This action cannot be undone.{' '} - {selectedSnippets.length === 1 - ? `Are you sure you want to delete '${selectedSnippets[0]?.name}'?` - : `Are you sure you want to delete the selected ${selectedSnippets.length} quer${selectedSnippets.length > 1 ? 'ies' : 'y'}?`} -

-
- - setSelectedFolderToDelete(undefined)} - onConfirm={onConfirmDeleteFolder} - alert={{ - title: 'This action cannot be undone', - description: - 'All SQL snippets within the folder will be permanently removed, and cannot be recovered.', - }} - > -

- Are you sure you want to delete the folder '{selectedFolderToDelete?.name}'? -

-
- - ) -} diff --git a/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.utils.ts b/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.utils.ts deleted file mode 100644 index 0a412f3c82e..00000000000 --- a/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorNav.utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { SnippetFolderResponse } from 'data/content/sql-folders-query' - -export interface TreeViewItemProps { - id: string | number - name: string - parent: number | string | null - children: any[] - metadata?: any -} - -export const ROOT_NODE = { id: 0, name: '', parent: null, children: [] } - -// [Joshen] At the moment this is only tuned for single level folders -// Will need to relook at this for multi level folders, -export const formatFolderResponseForTreeView = ( - response?: SnippetFolderResponse -): TreeViewItemProps[] => { - if (response === undefined) return [ROOT_NODE] - - const { folders, contents } = response - - const formattedFolders = - folders?.map((folder) => { - const { id, name } = folder - return { - id, - name, - parent: 0, - isBranch: true, - children: - contents?.filter((content) => content.folder_id === id).map((content) => content.id) ?? - [], - metadata: folder, - } - }) || [] - - const formattedContents = - contents?.map((content) => { - const { id, name, folder_id } = content - return { id, name, parent: folder_id ?? 0, children: [], metadata: content } - }) || [] - - const root = { - id: 0, - name: '', - parent: null, - children: [ - ...(folders || [])?.map((folder) => folder.id), - ...(contents || []).filter((content) => !content.folder_id)?.map((content) => content.id), - ], - } - - return [root, ...formattedFolders, ...formattedContents] -} diff --git a/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorTreeViewItem.tsx b/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorTreeViewItem.tsx deleted file mode 100644 index 00714352f99..00000000000 --- a/apps/studio/components/layouts/explorer/SQLEditorNavV2/SQLEditorTreeViewItem.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { Copy, Download, Edit, ExternalLink, Lock, Move, Plus, Share, Trash } from 'lucide-react' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { useState } from 'react' -import { toast } from 'sonner' - -import { IS_PLATFORM } from 'common' -import { useParams } from 'common/hooks/useParams' -import { getSQLSnippetFolders } from 'data/content/sql-folders-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useProfile } from 'lib/profile' -import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' -import { - ContextMenuContent_Shadcn_, - ContextMenuItem_Shadcn_, - ContextMenuSeparator_Shadcn_, - ContextMenuTrigger_Shadcn_, - ContextMenu_Shadcn_, - TreeViewItem, -} from 'ui' - -interface SQLEditorTreeViewItemProps { - element: any - level: number - isBranch: boolean - isSelected: boolean - isOpened?: boolean - isExpanded?: boolean - isMultiSelected?: boolean - status?: 'editing' | 'saving' | 'idle' - getNodeProps: () => any - onSelectCreate?: () => void - onSelectDelete?: () => void - onSelectRename?: () => void - onSelectMove?: () => void - onSelectShare?: () => void - onSelectUnshare?: () => void - onSelectDownload?: () => void - onSelectCopyPersonal?: () => void - onSelectDeleteFolder?: () => void - onEditSave?: (name: string) => void - onMultiSelect?: (id: string) => void -} - -export const SQLEditorTreeViewItem = ({ - element, - isBranch, - isOpened, - isExpanded, - level, - status, - isSelected, - isMultiSelected, - getNodeProps, - onSelectCreate, - onSelectDelete, - onSelectRename, - onSelectMove, - onSelectShare, - onSelectUnshare, - onSelectDownload, - onSelectCopyPersonal, - onEditSave, - onMultiSelect, -}: SQLEditorTreeViewItemProps) => { - const router = useRouter() - const { id, ref } = useParams() - const { profile } = useProfile() - const { className, onClick } = getNodeProps() - const snapV2 = useSqlEditorV2StateSnapshot() - - const [isFetching, setIsFetching] = useState(false) - - const isOwner = profile?.id === element?.metadata.owner_id - const isSharedSnippet = element.metadata.visibility === 'project' - - const isEditing = status === 'editing' - const isSaving = status === 'saving' - - const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', { - resource: { type: 'sql', owner_id: profile?.id }, - subject: { id: profile?.id }, - }) - - // [Joshen] Folder contents are loaded on demand too - const onOpenFolder = async (id: string) => { - if (!ref) return console.error('Project ref is required') - - try { - setIsFetching(true) - const { contents } = await getSQLSnippetFolders({ projectRef: ref, folderId: id }) - contents?.forEach((snippet) => { - snapV2.addSnippet({ projectRef: ref, snippet }) - }) - } catch (error: any) { - toast.error(`Failed to retrieve folder contents: ${error.message}`) - } finally { - setIsFetching(false) - } - } - - return ( - <> - - - { - if (onEditSave !== undefined) onEditSave(value) - }} - onClick={(e) => { - if (!isBranch) { - if (!e.shiftKey) { - const schema = (router.query.schema as string) || 'public' - router.push(`/project/${ref}/sql/${element.id}?schema=${schema}`) - } else if (id !== 'new') { - onMultiSelect?.(element.id) - } else { - const schema = (router.query.schema as string) || 'public' - router.push(`/project/${ref}/sql/${element.id}?schema=${schema}`) - } - } else { - // Prevent expanding folder while editing text - // as the user may double click to select etc - if (isEditing) { - return - } - - onClick(e) - if (!isExpanded) onOpenFolder(element.id) - } - }} - /> - - e.stopPropagation()}> - {isBranch ? ( - <> - {onSelectCreate !== undefined && ( - onSelectCreate()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Create new snippet - - )} - {onSelectRename !== undefined && isOwner && ( - onSelectRename()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Rename folder - - )} - {onSelectDelete !== undefined && isOwner && ( - <> - - onSelectDelete()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Delete folder - - - )} - - ) : isMultiSelected ? ( - <> - {onSelectMove !== undefined && ( - onSelectMove()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Move selected queries - - )} - - {onSelectDelete !== undefined && ( - onSelectDelete()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Delete selected queries - - )} - - ) : ( - <> - {}} - onFocusCapture={(e) => e.stopPropagation()} - > - - - Open in new tab - - - - {onSelectRename !== undefined && isOwner && ( - onSelectRename()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Rename query - - )} - {onSelectMove !== undefined && isOwner && ( - onSelectMove()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Move query - - )} - {onSelectShare !== undefined && !isSharedSnippet && canCreateSQLSnippet && ( - onSelectShare()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Share query with team - - )} - {onSelectUnshare !== undefined && isSharedSnippet && isOwner && ( - onSelectUnshare()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Unshare query with team - - )} - {onSelectCopyPersonal !== undefined && - isSharedSnippet && - !isOwner && - canCreateSQLSnippet && ( - onSelectCopyPersonal()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Duplicate personal copy - - )} - {onSelectDownload !== undefined && IS_PLATFORM && ( - onSelectDownload()} - onFocusCapture={(e) => e.stopPropagation()} - > - - Download as migration file - - )} - {onSelectDelete !== undefined && isOwner && ( - <> - - onSelectDelete()}> - - Delete query - - - )} - - )} - - - - ) -} diff --git a/apps/studio/components/layouts/explorer/TableEditorMenu.tsx b/apps/studio/components/layouts/explorer/TableEditorMenu.tsx deleted file mode 100644 index 294f35d3ba4..00000000000 --- a/apps/studio/components/layouts/explorer/TableEditorMenu.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { partition } from 'lodash' -import { Edit2, Filter, Pointer } from 'lucide-react' -import { useEffect, useMemo, useState } from 'react' - -import { useParams } from 'common' -import { ProtectedSchemaModal } from 'components/interfaces/Database/ProtectedSchemaWarning' -import AlertError from 'components/ui/AlertError' -import InfiniteList from 'components/ui/InfiniteList' -import SchemaSelector from 'components/ui/SchemaSelector' -import { useSchemasQuery } from 'data/database/schemas-query' -import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' -import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query' -import { useTableEditorQuery } from 'data/table-editor/table-editor-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useLocalStorage } from 'hooks/misc/useLocalStorage' -import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' -import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas' -import { useTableEditorStateSnapshot } from 'state/table-editor' -import { - Button, - Checkbox_Shadcn_, - Label_Shadcn_, - PopoverContent_Shadcn_, - PopoverTrigger_Shadcn_, - Popover_Shadcn_, - Skeleton, -} from 'ui' -import { - InnerSideBarEmptyPanel, - InnerSideBarFilterSearchInput, - InnerSideBarFilterSortDropdown, - InnerSideBarFilterSortDropdownItem, - InnerSideBarFilters, -} from 'ui-patterns/InnerSideMenu' -import { useProjectContext } from '../ProjectLayout/ProjectContext' -import EntityListItem from './EntityListItem' -import { SchemaButton } from './schema-button' -import { TableMenuEmptyState } from './TableMenuEmptyState' - -const TableEditorMenu = () => { - const { id: _id } = useParams() - const id = _id ? Number(_id) : undefined - const snap = useTableEditorStateSnapshot() - const { selectedSchema, setSelectedSchema } = useQuerySchemaState() - - const [showModal, setShowModal] = useState(false) - const [searchText, setSearchText] = useState('') - const [visibleTypes, setVisibleTypes] = useState(Object.values(ENTITY_TYPE)) - const [sort, setSort] = useLocalStorage<'alphabetical' | 'grouped-alphabetical'>( - 'table-editor-sort', - 'alphabetical' - ) - - const { project } = useProjectContext() - const { - data, - isLoading, - isSuccess, - isError, - error, - hasNextPage, - isFetchingNextPage, - fetchNextPage, - } = useEntityTypesQuery( - { - projectRef: project?.ref, - connectionString: project?.connectionString, - schemas: [selectedSchema], - search: searchText.trim() || undefined, - sort, - filterTypes: visibleTypes, - }, - { - keepPreviousData: Boolean(searchText), - } - ) - - const entityTypes = useMemo( - () => data?.pages.flatMap((page) => page.data.entities), - [data?.pages] - ) - - const { data: schemas } = useSchemasQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - }) - - const schema = schemas?.find((schema) => schema.name === selectedSchema) - const canCreateTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') - - const [protectedSchemas] = partition( - (schemas ?? []).sort((a, b) => a.name.localeCompare(b.name)), - (schema) => EXCLUDED_SCHEMAS.includes(schema?.name ?? '') - ) - const isLocked = protectedSchemas.some((s) => s.id === schema?.id) - - const { data: selectedTable } = useTableEditorQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - id, - }) - - useEffect(() => { - if (selectedTable?.schema) { - setSelectedSchema(selectedTable.schema) - } - }, [selectedTable?.schema]) - - return ( - <> -
-
-

Postgres

-
- { - setSearchText('') - setSelectedSchema(name) - }} - onSelectCreateSchema={() => snap.onAddSchema()} - /> -
-
- {/*
*/} - {/* { - setSearchText('') - setSelectedSchema(name) - }} - onSelectCreateSchema={() => snap.onAddSchema()} - /> */} - {/*
*/} - {/* {!isLocked ? ( - } - type="default" - className="justify-start" - onClick={snap.onAddTable} - tooltip={{ - content: { - side: 'bottom', - text: !canCreateTables - ? 'You need additional permissions to create tables' - : undefined, - }, - }} - > - New table - - ) : ( */} - {/* - Viewing protected schema - -

- This schema is managed by Supabase and is read-only through the table editor -

- -
-
*/} - {/* )} */} - {/*
*/} - {/*
*/} -
- - { - setSearchText(e.target.value) - }} - value={searchText} - placeholder="Search tables..." - > - setSort(value)} - > - - Alphabetical - - - Entity Type - - - - - - -
- ))} -
-
- - - - - {isLoading && ( -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- )} - {isError && ( - - )} - {isSuccess && ( - <> - {searchText.length === 0 && (entityTypes?.length ?? 0) <= 0 && ( - - )} - {searchText.length > 0 && (entityTypes?.length ?? 0) <= 0 && ( - - )} - {(entityTypes?.length ?? 0) > 0 && ( -
- - 28} - hasNextPage={hasNextPage} - isLoadingNextPage={isFetchingNextPage} - onLoadNextPage={() => fetchNextPage()} - /> -
- )} - - )} -
-
- - setShowModal(false)} /> - - ) -} - -export default TableEditorMenu diff --git a/apps/studio/components/layouts/explorer/empty-state-panel.tsx b/apps/studio/components/layouts/explorer/empty-state-panel.tsx deleted file mode 100644 index 2192227ecb1..00000000000 --- a/apps/studio/components/layouts/explorer/empty-state-panel.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Markdown } from 'components/interfaces/Markdown' -import { Sparkles } from 'lucide-react' -import { Button } from 'ui' - -interface EmptyStatePanelProps { - title?: string - description?: string - buttonText?: string - onButtonClick?: () => void - aiButtonText?: string - onAiButtonClick?: () => void - showIcons?: boolean -} - -export const EmptyStatePanel = ({ - title = 'Active issues', - description = 'Active issues represent work that is currently in flight or should be worked on next. There are currently no active issues in this team.', - buttonText = 'Create new issue', - onButtonClick, - aiButtonText, - onAiButtonClick, - showIcons = true, -}: EmptyStatePanelProps) => { - return ( -
- {showIcons && ( -
- {[...Array(4)].map((_, i) => ( -
-
-
- {i === 1 && ( -
-
-
- )} - {i === 2 && ( -
-
-
- )} - {i === 3 && ( -
-
-
- )} -
- ))} -
- )} -
-

{title}

- -
- {(buttonText || aiButtonText) && ( -
- {buttonText && ( - - )} - {aiButtonText && ( - - )} -
- )} -
- ) -} diff --git a/apps/studio/components/layouts/explorer/layout.tsx b/apps/studio/components/layouts/explorer/layout.tsx deleted file mode 100644 index ed91b7ea94f..00000000000 --- a/apps/studio/components/layouts/explorer/layout.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' -import { OngoingQueriesPanel } from 'components/interfaces/SQLEditor/OngoingQueriesPanel' -import NoPermission from 'components/ui/NoPermission' -import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' -import { PanelLeftClose, PanelLeftOpen } from 'lucide-react' -import { ReactNode, useEffect, useMemo, useState } from 'react' -import { Button, cn, ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui' -import { useSnapshot } from 'valtio' -import { ProjectLayoutWithAuth } from '../ProjectLayout/ProjectLayout' -import { ExplorerTabs } from '../tabs/explorer-tabs' -import { sidebarState } from '../tabs/sidebar-state' -import { SQLEditorMenu } from './SQLEditorMenu' -import TableEditorMenu from './TableEditorMenu' -import { CollapseButton } from '../tabs/collapse-button' -import { useParams } from 'common' -import { usePathname } from 'next/navigation' - -export interface ExplorerLayoutProps { - children: ReactNode - hideTabs?: boolean -} - -export const ExplorerLayout = ({ children }: ExplorerLayoutProps) => { - const { ref } = useParams() - const pathname = usePathname() - const [showOngoingQueries, setShowOngoingQueries] = useState(false) - - const isPermissionsLoaded = usePermissionsLoaded() - const canReadTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables') - - const productMenu = useMemo( - () => ( - - - - - - - setShowOngoingQueries(true)} - /> - - -
- -
-
- ), - [] - ) - - if (isPermissionsLoaded && !canReadTables) { - return ( - - - - ) - } - - const hideTabs = pathname === `/project/${ref}/explorer` - - return ( - -
-
- {hideTabs && } - {!hideTabs && } -
-
{children}
-
- setShowOngoingQueries(false)} - /> -
- ) -} diff --git a/apps/studio/components/layouts/explorer/schema-button.tsx b/apps/studio/components/layouts/explorer/schema-button.tsx deleted file mode 100644 index c027aeb1bd7..00000000000 --- a/apps/studio/components/layouts/explorer/schema-button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useParams } from 'common' -import { Schema } from 'data/database/schemas-query' -import { Workflow } from 'lucide-react' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { cn, TreeViewItemVariant } from 'ui' - -export function SchemaButton({ schema }: { schema: Schema | undefined }) { - const { ref } = useParams() - const { asPath } = useRouter() - - const active = asPath.includes(`/project/${ref}/explorer/schema/${schema?.name}`) - - return ( - - {active &&
} - - {schema?.name} visualizer - - ) -} diff --git a/apps/studio/components/layouts/tabs/explorer-tabs.tsx b/apps/studio/components/layouts/tabs/explorer-tabs.tsx index 286b090f92e..3c0c305b30d 100644 --- a/apps/studio/components/layouts/tabs/explorer-tabs.tsx +++ b/apps/studio/components/layouts/tabs/explorer-tabs.tsx @@ -3,9 +3,8 @@ import { horizontalListSortingStrategy, SortableContext, useSortable } from '@dn import { CSS } from '@dnd-kit/utilities' import { useParams } from 'common' import { EntityTypeIcon } from 'components/tabs/entity-type-icon' -import { TabIcon } from 'components/tabs/TabIcon' import { AnimatePresence, motion } from 'framer-motion' -import { Plus, X } from 'lucide-react' +import { CirclePlus, Plus, X } from 'lucide-react' import { useRouter } from 'next/router' import { useMemo } from 'react' import { @@ -97,7 +96,6 @@ const SortableTab = ({ )} {...listeners} > - {/* */}
@@ -224,7 +222,7 @@ export function ExplorerTabs({ onClose }: TabsProps) { 'hover:bg-surface-300 dark:hover:bg-surface-100' )} > - +
New
diff --git a/apps/studio/components/layouts/tabs/new-tab.tsx b/apps/studio/components/layouts/tabs/new-tab.tsx index e8e7ea5a7af..5a7e0fa4183 100644 --- a/apps/studio/components/layouts/tabs/new-tab.tsx +++ b/apps/studio/components/layouts/tabs/new-tab.tsx @@ -40,15 +40,6 @@ export function NewTab() { isBeta: false, onClick: handleNewQuery, }, - - { - icon: , - title: 'Upload CSV', - description: 'Import data from CSV files', - bgColor: 'bg-green-500', - isBeta: false, - onClick: handleNewQuery, - }, ] const sqlEditorActions = [ @@ -60,28 +51,6 @@ export function NewTab() { isBeta: false, onClick: handleNewQuery, }, - { - icon: ( -
- -
- -
-
- ), - title: 'New SQL Snippet Chart', - description: 'Create data visualizations', - bgColor: 'bg-green-500', - onClick: handleNewQuery, - // isBeta: true, - }, - // { - // icon: , - // title: 'New Report', - // description: 'Create data visualizations', - // bgColor: 'bg-orange-500', - // isBeta: true, - // }, ] const actions = editor === 'sql' ? sqlEditorActions : tableEditorActions @@ -89,7 +58,7 @@ export function NewTab() { return (
-
+
{actions.map((item, i) => ( ))} diff --git a/apps/studio/components/tabs/TabIcon.tsx b/apps/studio/components/tabs/TabIcon.tsx deleted file mode 100644 index 36e2cb734ed..00000000000 --- a/apps/studio/components/tabs/TabIcon.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { CirclePlus, Eye, Table2, Workflow } from 'lucide-react' -import { TabType } from 'state/tabs' -import { cn, SQL_ICON } from 'ui' - -interface TabIconProps { - size?: number - strokeWidth?: number - className?: string - type: TabType -} - -export const TabIcon = ({ type, size = 14, strokeWidth = 1.5, className }: TabIconProps) => { - switch (type) { - case 'schema': - return ( - - ) - case 'table': - return ( - - ) - case 'view': - return ( - - ) - case 'sql': - return ( - - ) - default: - return ( - - ) - } -}