import { useCallback, useEffect, useRef } from 'react' import { Entity } from '@/data/entity-types/entity-types-infinite-query' import useLatest from '@/hooks/misc/useLatest' import { createTabId, editorEntityTypes, useTabsStateSnapshot } from '@/state/tabs' export function useTableEditorTabsCleanUp() { const tabs = useTabsStateSnapshot() const tabMapRef = useLatest(tabs.tabsMap) const recentItemsRef = useLatest(tabs.recentItems) const openTabsRef = useLatest(tabs.openTabs) return useCallback(({ schemas, entities }: { schemas: string[]; entities: Entity[] }) => { // Identity all entities by tab ID const entitiesById: string[] = entities.map((x: Entity) => createTabId(x.type, { id: x.id })) const recentItemsFilteredToSchemas: string[] = [] for (const schema of schemas) { recentItemsFilteredToSchemas.push( ...recentItemsRef.current.filter((x) => x.metadata?.schema === schema).map((x) => x.id) ) } const recentItemsToRemove = [ ...recentItemsFilteredToSchemas.filter((entityId) => !entitiesById.includes(entityId)), ] tabs.removeRecentItems(recentItemsToRemove) const tabsFilteredToSchemas: string[] = [] for (const schema of schemas) { tabsFilteredToSchemas.push( ...openTabsRef.current.filter((tabId) => { const tab = tabMapRef.current[tabId] return tab?.metadata?.schema === schema }) ) } const tableEditorTabsToBeCleaned = [ ...tabsFilteredToSchemas.filter( (id) => !id.startsWith('sql') && !id.startsWith('schema') && !entitiesById.includes(id) ), ] // perform tabs cleanup tabs.removeTabs(tableEditorTabsToBeCleaned) // [Joshen] Validate for opened tabs, if their label matches the entity's name - update label if not // As the entity could've been renamed outside of the table editor // e.g Using the SQL editor to rename the entity const openTabs = openTabsRef.current .map((id) => tabMapRef.current[id]) .filter((tab) => !!tab && editorEntityTypes['table']?.includes(tab.type)) openTabs.forEach((tab) => { const entity = entities?.find((x) => tab.metadata?.tableId === x.id) if (!!entity && entity.name !== tab.label) tabs.updateTab(tab.id, { label: entity.name }) }) }, []) } export function useSqlEditorTabsCleanup() { const tabs = useTabsStateSnapshot() const tabMapRef = useLatest(tabs.tabsMap) const openTabsRef = useLatest(tabs.openTabs) return useCallback(({ snippets }: { snippets: { id: string; type: string; name: string }[] }) => { // these are tabs that are static content // these canot be removed from localstorage based on this query request const IGNORED_TAB_IDS = ['sql-templates', 'sql-quickstarts'] // Identify all SQL snippets / content by their tab ids const currentContentIds = [ ...snippets .filter((content) => content.type === 'sql') .map((content) => createTabId('sql', { id: content.id })), // append ignored tab IDs ...IGNORED_TAB_IDS, ] // Remove any snippet tabs that might no longer be existing (removed outside of the dashboard session) const snippetTabsToBeCleaned = openTabsRef.current.filter( (id: string) => id.startsWith('sql') && !currentContentIds.includes(id) ) tabs.removeTabs(snippetTabsToBeCleaned) // Remove any recent items that might no longer be existing (removed outside of the dashboard session) const recentItems = tabs.getRecentItemsByType('sql') tabs.removeRecentItems( recentItems ? recentItems.filter((item) => !currentContentIds.includes(item.id)).map((item) => item.id) : [] ) // [Joshen] Validate for opened tabs, if their label matches the snippet's name - update label if not // As the snippets name could've been updated outside of the SQL Editor session // e.g for a shared snippet, the owner could've updated the name of the snippet const openSqlTabs = openTabsRef.current .map((id) => tabMapRef.current[id]) .filter((tab) => !!tab && editorEntityTypes['sql']?.includes(tab.type)) openSqlTabs.forEach((tab) => { const snippet = snippets?.find((x) => tab.metadata?.sqlId === x.id) if (!!snippet && snippet.name !== tab.label) tabs.updateTab(tab.id, { label: snippet.name }) }) }, []) } interface UseTabsScrollOptions { activeTab: string | null | undefined tabCount: number } export function useTabsScroll({ activeTab, tabCount }: UseTabsScrollOptions) { const tabsListRef = useRef(null) const prevTabCountRef = useRef(tabCount) const isInitialMount = useRef(true) useEffect(() => { if (tabsListRef.current) { tabsListRef.current.scrollLeft = tabsListRef.current.scrollWidth } }, []) useEffect(() => { if (isInitialMount.current) { isInitialMount.current = false return } if (!tabsListRef.current) return const tabCountIncreased = tabCount > prevTabCountRef.current if (tabCountIncreased) { tabsListRef.current.scrollLeft = tabsListRef.current.scrollWidth } else if (activeTab) { const activeTabElement = tabsListRef.current.querySelector( `[data-state="active"]` ) as HTMLElement if (activeTabElement) { activeTabElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest', }) } } prevTabCountRef.current = tabCount }, [activeTab, tabCount]) return { tabsListRef } }