diff --git a/apps/studio/components/grid/SupabaseGrid.utils.ts b/apps/studio/components/grid/SupabaseGrid.utils.ts index 5f6ae0cf5fa..cea90123024 100644 --- a/apps/studio/components/grid/SupabaseGrid.utils.ts +++ b/apps/studio/components/grid/SupabaseGrid.utils.ts @@ -1,6 +1,6 @@ import AwesomeDebouncePromise from 'awesome-debounce-promise' -import { compact } from 'lodash' -import { useEffect } from 'react' +import { compact, filter } from 'lodash' +import { useEffect, useState } from 'react' import { CalculatedColumn, CellKeyboardEvent } from 'react-data-grid' import type { Filter, SavedState } from 'components/grid/types' @@ -11,6 +11,9 @@ import { FilterOperatorOptions } from './components/header/filter/Filter.constan import { STORAGE_KEY_PREFIX } from './constants' import type { Sort, SupaColumn, SupaTable } from './types' import { formatClipboardValue } from './utils/common' +import { parseAsArrayOf, parseAsBoolean, parseAsString, useQueryStates } from 'nuqs' + +export const LOAD_TAB_FROM_CACHE_PARAM = 'loadFromCache' export function formatSortURLParams(tableName: string, sort?: string[]): Sort[] { if (Array.isArray(sort)) { @@ -176,41 +179,60 @@ export const saveTableEditorStateToLocalStorageDebounced = AwesomeDebouncePromis 500 ) -export function useLoadTableEditorStateFromLocalStorageIntoUrl({ +function getLatestParams() { + const queryParams = new URLSearchParams(window.location.search) + const sort = queryParams.getAll('sort') + const filter = queryParams.getAll('filter') + const loadFromCache = !!queryParams.get(LOAD_TAB_FROM_CACHE_PARAM) + return { sort, filter, loadFromCache } +} + +export function useSyncTableEditorStateFromLocalStorageWithUrl({ projectRef, table, }: { projectRef: string | undefined table: Entity | undefined }) { - const [_, setParams] = useUrlState({ - arrayKeys: ['sort', 'filter'], - }) + const [urlParams, updateUrlParams] = useQueryStates( + { + sort: parseAsArrayOf(parseAsString).withDefault([]), + filter: parseAsArrayOf(parseAsString).withDefault([]), + [LOAD_TAB_FROM_CACHE_PARAM]: parseAsBoolean.withDefault(false), + }, + { + history: 'replace', + } + ) + useEffect(() => { if (!projectRef || !table) { return } - const searchParams = new URLSearchParams(window.location.search) + // `urlParams` from `useQueryStates` can be stale so always get the latest from the URL + const latestUrlParams = getLatestParams() - const savedState = loadTableEditorStateFromLocalStorage(projectRef, table.name, table.schema) - - // If no sort params are set, use saved state - - let params: { sort?: string[]; filter?: string[] } | undefined - - if (searchParams.getAll('sort').length <= 0 && savedState?.sorts) { - params = { ...params, sort: savedState.sorts } + if (latestUrlParams.loadFromCache) { + const savedState = loadTableEditorStateFromLocalStorage(projectRef, table.name, table.schema) + updateUrlParams( + { + sort: savedState?.sorts ?? [], + filter: savedState?.filters ?? [], + loadFromCache: false, + }, + { clearOnDefault: true } + ) + } else { + saveTableEditorStateToLocalStorage({ + projectRef, + tableName: table.name, + schema: table.schema, + sorts: latestUrlParams.sort, + filters: latestUrlParams.filter, + }) } - - if (searchParams.getAll('filter').length <= 0 && savedState?.filters) { - params = { ...params, filter: savedState.filters } - } - - if (params) { - setParams((prevParams) => ({ ...prevParams, ...params })) - } - }, [projectRef, table]) + }, [urlParams, table, projectRef]) } export const handleCopyCell = ( diff --git a/apps/studio/components/grid/hooks/useSaveTableEditorState.ts b/apps/studio/components/grid/hooks/useSaveTableEditorState.ts deleted file mode 100644 index cab483a243b..00000000000 --- a/apps/studio/components/grid/hooks/useSaveTableEditorState.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useCallback } from 'react' - -import { saveTableEditorStateToLocalStorage } from 'components/grid/SupabaseGrid.utils' -import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { useTableEditorTableStateSnapshot } from 'state/table-editor-table' - -/** - * Hook for saving state and triggering side effects. - */ -export function useSaveTableEditorState() { - const { data: project } = useSelectedProjectQuery() - const snap = useTableEditorTableStateSnapshot() - - const saveDataAndTriggerSideEffects = useCallback( - (dataToSave: { filters?: string[]; sorts?: string[] }) => { - const projectRef = project?.ref - - if (!projectRef) { - return console.warn( - '[useSaveTableEditorState] ProjectRef missing, cannot save or trigger side effects.' - ) - } - - try { - snap.setPage(1) - snap.setEnforceExactCount(false) - - const tableName = snap.table?.name - const schema = snap.table?.schema - - if (tableName) { - saveTableEditorStateToLocalStorage({ - projectRef, - tableName, - schema, - ...dataToSave, - }) - } else { - console.warn('[useSaveTableEditorState] Table name missing, skipping localStorage save.') - } - } catch (error) { - console.error('[useSaveTableEditorState] Error during interaction with snapshot:', error) - } - }, - [snap, project] - ) - - const saveFiltersAndTriggerSideEffects = useCallback( - (urlFilters: string[]) => saveDataAndTriggerSideEffects({ filters: urlFilters }), - [saveDataAndTriggerSideEffects] - ) - const saveSortsAndTriggerSideEffects = useCallback( - (urlSorts: string[]) => saveDataAndTriggerSideEffects({ sorts: urlSorts }), - [saveDataAndTriggerSideEffects] - ) - - return { saveFiltersAndTriggerSideEffects, saveSortsAndTriggerSideEffects } -} diff --git a/apps/studio/components/grid/hooks/useTableFilter.ts b/apps/studio/components/grid/hooks/useTableFilter.ts index 0181bbce9ca..a93df6386bb 100644 --- a/apps/studio/components/grid/hooks/useTableFilter.ts +++ b/apps/studio/components/grid/hooks/useTableFilter.ts @@ -3,7 +3,6 @@ import { useCallback } from 'react' import { filtersToUrlParams, formatFilterURLParams } from 'components/grid/SupabaseGrid.utils' import type { Filter } from 'components/grid/types' import { useTableEditorFiltersSort } from 'hooks/misc/useTableEditorFiltersSort' -import { useSaveTableEditorState } from './useSaveTableEditorState' /** * Hook for managing table filter URL parameters and saving. @@ -11,7 +10,6 @@ import { useSaveTableEditorState } from './useSaveTableEditorState' */ export function useTableFilter() { const { filters: urlFilters, setParams } = useTableEditorFiltersSort() - const { saveFiltersAndTriggerSideEffects } = useSaveTableEditorState() const filters = formatFilterURLParams(urlFilters) @@ -19,9 +17,8 @@ export function useTableFilter() { (appliedFilters: Filter[]) => { const newUrlFilters = filtersToUrlParams(appliedFilters) setParams((prevParams) => ({ ...prevParams, filter: newUrlFilters })) - saveFiltersAndTriggerSideEffects(newUrlFilters) }, - [setParams, saveFiltersAndTriggerSideEffects] + [setParams] ) return { diff --git a/apps/studio/components/grid/hooks/useTableSort.ts b/apps/studio/components/grid/hooks/useTableSort.ts index f114d9f1753..deb26d373f9 100644 --- a/apps/studio/components/grid/hooks/useTableSort.ts +++ b/apps/studio/components/grid/hooks/useTableSort.ts @@ -4,19 +4,16 @@ import { formatSortURLParams, sortsToUrlParams } from 'components/grid/SupabaseG import type { Sort } from 'components/grid/types' import { useTableEditorFiltersSort } from 'hooks/misc/useTableEditorFiltersSort' import { useTableEditorTableStateSnapshot } from 'state/table-editor-table' -import { useSaveTableEditorState } from './useSaveTableEditorState' /** * Hook for managing table sort URL parameters and saving. * Uses snapshot ONLY to get table name for formatting/mapping. - * Uses useSaveTableEditorState for saving and side effects. * Does NOT format initial sorts (needs table name externally). * Does NOT interact with snapshot directly. */ export function useTableSort() { const { sorts: urlSorts, setParams } = useTableEditorFiltersSort() const snap = useTableEditorTableStateSnapshot() - const { saveSortsAndTriggerSideEffects } = useSaveTableEditorState() const tableName = useMemo(() => snap.table?.name || '', [snap]) @@ -36,10 +33,8 @@ export function useTableSort() { const newUrlSorts = sortsToUrlParams(sortsWithTable) setParams((prevParams) => ({ ...prevParams, sort: newUrlSorts })) - - saveSortsAndTriggerSideEffects(newUrlSorts) }, - [snap, setParams, saveSortsAndTriggerSideEffects] + [snap, setParams] ) /** diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx index c3800ff4cc5..5c354a6d784 100644 --- a/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaTableNode.tsx @@ -1,3 +1,4 @@ +import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils' import { DiamondIcon, ExternalLink, Fingerprint, Hash, Key, Table2 } from 'lucide-react' import Link from 'next/link' import { Handle, NodeProps } from 'reactflow' @@ -67,7 +68,9 @@ const TableNode = ({ {data.id && !placeholder && ( diff --git a/apps/studio/components/interfaces/Database/Tables/TableList.tsx b/apps/studio/components/interfaces/Database/Tables/TableList.tsx index a54046da943..f88f13ebe16 100644 --- a/apps/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/TableList.tsx @@ -63,6 +63,7 @@ import { } from 'ui' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import { formatAllEntities } from './Tables.utils' +import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils' interface TableListProps { onAddTable: () => void @@ -488,7 +489,9 @@ export const TableList = ({ - router.push(`/project/${project?.ref}/editor/${x.id}`) + router.push( + `/project/${project?.ref}/editor/${x.id}?${LOAD_TAB_FROM_CACHE_PARAM}=true` + ) } onMouseEnter={() => prefetchEditorTablePage({ diff --git a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 4f7ac11ccf0..b9c6d19ce1c 100644 --- a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -5,7 +5,7 @@ import { useCallback } from 'react' import { useParams } from 'common' import { SupabaseGrid } from 'components/grid/SupabaseGrid' -import { useLoadTableEditorStateFromLocalStorageIntoUrl } from 'components/grid/SupabaseGrid.utils' +import { useSyncTableEditorStateFromLocalStorageWithUrl } from 'components/grid/SupabaseGrid.utils' import { Entity, isForeignTable, @@ -41,13 +41,12 @@ export const TableGridEditor = ({ const tabs = useTabsStateSnapshot() - useLoadTableEditorStateFromLocalStorageIntoUrl({ + useSyncTableEditorStateFromLocalStorageWithUrl({ projectRef, table: selectedTable, }) const [{ view: selectedView = 'data' }] = useUrlState() - const { can: canEditTables } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' diff --git a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx index 84dff45cae3..e1291923de4 100644 --- a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx @@ -9,7 +9,7 @@ import { MAX_EXPORT_ROW_COUNT, MAX_EXPORT_ROW_COUNT_MESSAGE, } from 'components/grid/components/header/Header' -import { parseSupaTable } from 'components/grid/SupabaseGrid.utils' +import { LOAD_TAB_FROM_CACHE_PARAM, parseSupaTable } from 'components/grid/SupabaseGrid.utils' import { formatTableRowsToSQL, getEntityLintDetails, @@ -237,7 +237,7 @@ const EntityListItem: ItemRenderer = ({ { // Handle redirect to last opened table tab, or last table tab if (lastOpenedTable !== undefined) { - router.push(`/project/${projectRef}/editor/${history.editor}`) + router.push( + `/project/${projectRef}/editor/${history.editor}?${LOAD_TAB_FROM_CACHE_PARAM}=true` + ) } else if (lastTabId) { const lastTab = tabStore.tabsMap[lastTabId] - if (lastTab) router.push(`/project/${projectRef}/editor/${lastTab.metadata?.tableId}`) + if (lastTab) + router.push( + `/project/${projectRef}/editor/${lastTab.metadata?.tableId}?${LOAD_TAB_FROM_CACHE_PARAM}=true` + ) } } }, [isHistoryLoaded]) diff --git a/apps/studio/state/tabs.tsx b/apps/studio/state/tabs.tsx index 348528335f3..f7ddd272e5d 100644 --- a/apps/studio/state/tabs.tsx +++ b/apps/studio/state/tabs.tsx @@ -1,3 +1,4 @@ +import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils' import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { partition } from 'lodash' @@ -286,7 +287,7 @@ function createTabsState(projectRef: string) { case 'f': case 'p': router.push( - `/project/${router.query.ref}/editor/${tab.metadata?.tableId}?schema=${tab.metadata?.schema}` + `/project/${router.query.ref}/editor/${tab.metadata?.tableId}?schema=${tab.metadata?.schema}&${LOAD_TAB_FROM_CACHE_PARAM}=true` ) break }