mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Chore/refactor dashboard history logic (#38586)
* Shift dashboard history logic outside of app state, into its own hook * Self remediate malformed history for sql editor * Update apps/studio/hooks/misc/useDashboardHistory.ts Co-authored-by: Jordi Enric <37541088+jordienr@users.noreply.github.com> * Update apps/studio/hooks/misc/useDashboardHistory.ts Co-authored-by: Jordi Enric <37541088+jordienr@users.noreply.github.com> --------- Co-authored-by: Jordi Enric <37541088+jordienr@users.noreply.github.com>
This commit is contained in:
1 parent
47842c7068
commit
deda982074
9 files changed
+110
-99
No files matched your search
@@ -5,28 +5,22 @@ import { toast } from 'sonner'
|
||||
import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsMFAEnabled, useParams } from 'common'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
|
||||
import useLatest from 'hooks/misc/useLatest'
|
||||
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
|
||||
// Ideally these could all be within a _middleware when we use Next 12
|
||||
const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||
const router = useRouter()
|
||||
const { ref, slug, id } = useParams()
|
||||
|
||||
const isLoggedIn = useIsLoggedIn()
|
||||
const snap = useAppStateSnapshot()
|
||||
const isUserMFAEnabled = useIsMFAEnabled()
|
||||
|
||||
const { data: organization } = useSelectedOrganizationQuery()
|
||||
|
||||
const [dashboardHistory, _, { isSuccess: isSuccessStorage }] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.DASHBOARD_HISTORY(ref ?? ''),
|
||||
{ editor: undefined, sql: undefined }
|
||||
)
|
||||
const isLoggedIn = useIsLoggedIn()
|
||||
const isUserMFAEnabled = useIsMFAEnabled()
|
||||
|
||||
const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
|
||||
const [__, setLastVisitedOrganization] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
|
||||
''
|
||||
@@ -106,22 +100,13 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||
useEffect(() => {
|
||||
if (ref !== undefined && id !== undefined) {
|
||||
if (router.pathname.endsWith('/sql/[id]') && id !== 'new') {
|
||||
snap.setDashboardHistory(ref, 'sql', id)
|
||||
setLastVisitedSnippet(id)
|
||||
} else if (router.pathname.endsWith('/editor/[id]')) {
|
||||
setLastVisitedTable(id)
|
||||
}
|
||||
if (router.pathname.endsWith('/editor/[id]')) {
|
||||
snap.setDashboardHistory(ref, 'editor', id)
|
||||
}
|
||||
}
|
||||
}, [ref, id])
|
||||
|
||||
useEffect(() => {
|
||||
// Load dashboard history into app state
|
||||
if (isSuccessStorage && ref) {
|
||||
snap.setDashboardHistory(ref, 'editor', dashboardHistory.editor)
|
||||
snap.setDashboardHistory(ref, 'sql', dashboardHistory.sql)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSuccessStorage, ref])
|
||||
}, [ref, id])
|
||||
|
||||
useEffect(() => {
|
||||
if (organization) {
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
isView,
|
||||
} from 'data/table-editor/table-editor-types'
|
||||
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
|
||||
import { useUrlState } from 'hooks/ui/useUrlState'
|
||||
import { useIsProtectedSchema } from 'hooks/useProtectedSchemas'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { TableEditorTableStateContextProvider } from 'state/table-editor-table'
|
||||
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
|
||||
import { Button } from 'ui'
|
||||
@@ -34,8 +34,8 @@ export const TableGridEditor = ({
|
||||
selectedTable,
|
||||
}: TableGridEditorProps) => {
|
||||
const router = useRouter()
|
||||
const appSnap = useAppStateSnapshot()
|
||||
const { ref: projectRef, id } = useParams()
|
||||
const { setLastVisitedTable } = useDashboardHistory()
|
||||
|
||||
const tabs = useTabsStateSnapshot()
|
||||
|
||||
@@ -58,10 +58,6 @@ export const TableGridEditor = ({
|
||||
const tabId = !!id ? tabs.openTabs.find((x) => x.endsWith(id)) : undefined
|
||||
const openTabs = tabs.openTabs.filter((x) => !x.startsWith('sql'))
|
||||
|
||||
const onClearDashboardHistory = useCallback(() => {
|
||||
if (projectRef) appSnap.setDashboardHistory(projectRef, 'editor', undefined)
|
||||
}, [appSnap, projectRef])
|
||||
|
||||
const onTableCreated = useCallback(
|
||||
(table: { id: number }) => {
|
||||
router.push(`/project/${projectRef}/editor/${table.id}`)
|
||||
@@ -74,9 +70,14 @@ export const TableGridEditor = ({
|
||||
if (selectedTable) {
|
||||
// Close tab
|
||||
const tabId = createTabId(selectedTable.entity_type, { id: selectedTable.id })
|
||||
tabs.handleTabClose({ id: tabId, router, editor: 'table', onClearDashboardHistory })
|
||||
tabs.handleTabClose({
|
||||
id: tabId,
|
||||
router,
|
||||
editor: 'table',
|
||||
onClearDashboardHistory: () => setLastVisitedTable(undefined),
|
||||
})
|
||||
}
|
||||
}, [onClearDashboardHistory, router, selectedTable, tabs])
|
||||
}, [router, selectedTable, tabs])
|
||||
|
||||
const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedTable?.schema ?? '' })
|
||||
|
||||
@@ -111,7 +112,7 @@ export const TableGridEditor = ({
|
||||
id: tabId,
|
||||
router,
|
||||
editor: 'table',
|
||||
onClearDashboardHistory,
|
||||
onClearDashboardHistory: () => setLastVisitedTable(undefined),
|
||||
})
|
||||
}}
|
||||
>
|
||||
@@ -122,7 +123,7 @@ export const TableGridEditor = ({
|
||||
asChild
|
||||
type="default"
|
||||
className="mt-2"
|
||||
onClick={() => appSnap.setDashboardHistory(projectRef, 'editor', undefined)}
|
||||
onClick={() => setLastVisitedTable(undefined)}
|
||||
>
|
||||
<Link href={`/project/${projectRef}/editor/${openTabs[0].split('-')[1]}`}>
|
||||
Close tab
|
||||
@@ -133,7 +134,7 @@ export const TableGridEditor = ({
|
||||
asChild
|
||||
type="default"
|
||||
className="mt-2"
|
||||
onClick={() => appSnap.setDashboardHistory(projectRef, 'editor', undefined)}
|
||||
onClick={() => setLastVisitedTable(undefined)}
|
||||
>
|
||||
<Link href={`/project/${projectRef}/editor`}>Head back</Link>
|
||||
</Button>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Plus, X } from 'lucide-react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
|
||||
import { editorEntityTypes, useTabsStateSnapshot, type Tab } from 'state/tabs'
|
||||
import {
|
||||
cn,
|
||||
@@ -32,7 +32,7 @@ import { TabPreview } from './TabPreview'
|
||||
export const EditorTabs = () => {
|
||||
const { ref, id } = useParams()
|
||||
const router = useRouter()
|
||||
const appSnap = useAppStateSnapshot()
|
||||
const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
|
||||
|
||||
const editor = useEditorType()
|
||||
const tabs = useTabsStateSnapshot()
|
||||
@@ -68,8 +68,10 @@ export const EditorTabs = () => {
|
||||
}
|
||||
|
||||
const onClearDashboardHistory = () => {
|
||||
if (ref && editor) {
|
||||
appSnap.setDashboardHistory(ref, editor === 'table' ? 'editor' : editor, undefined)
|
||||
if (editor === 'table') {
|
||||
setLastVisitedTable(undefined)
|
||||
} else if (editor === 'sql') {
|
||||
setLastVisitedSnippet(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
|
||||
import { useLocalStorageQuery } from './useLocalStorage'
|
||||
|
||||
type DashboardHistory = { editor?: string; sql?: string }
|
||||
const DEFAULT_HISTORY = { editor: undefined, sql: undefined }
|
||||
|
||||
export const useDashboardHistory = () => {
|
||||
// [Joshen] History should always refer to the project that the user is currently on
|
||||
const { ref } = useParams()
|
||||
|
||||
const [history, setHistory, { isSuccess }] = useLocalStorageQuery<DashboardHistory>(
|
||||
LOCAL_STORAGE_KEYS.DASHBOARD_HISTORY(ref ?? ''),
|
||||
DEFAULT_HISTORY
|
||||
)
|
||||
|
||||
const setLastVisitedTable = (id?: string) => {
|
||||
setHistory({ ...history, editor: id })
|
||||
}
|
||||
|
||||
const setLastVisitedSnippet = (id?: string) => {
|
||||
setHistory({ ...history, sql: id })
|
||||
}
|
||||
|
||||
return {
|
||||
history,
|
||||
setLastVisitedTable,
|
||||
setLastVisitedSnippet,
|
||||
isHistoryLoaded: isSuccess,
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
|
||||
import TableEditorLayout from 'components/layouts/TableEditorLayout/TableEditorLayout'
|
||||
import { TableEditorMenu } from 'components/layouts/TableEditorLayout/TableEditorMenu'
|
||||
import { NewTab } from 'components/layouts/Tabs/NewTab'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
|
||||
import { editorEntityTypes, useTabsStateSnapshot } from 'state/tabs'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
@@ -16,26 +16,28 @@ const TableEditorPage: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const tabStore = useTabsStateSnapshot()
|
||||
const appSnap = useAppStateSnapshot()
|
||||
const { history, isHistoryLoaded } = useDashboardHistory()
|
||||
|
||||
const onTableCreated = (table: { id: number }) => {
|
||||
router.push(`/project/${projectRef}/editor/${table.id}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const lastOpenedTab = appSnap.dashboardHistory.editor
|
||||
if (isHistoryLoaded) {
|
||||
const lastOpenedTable = history.editor
|
||||
const lastTabId = tabStore.openTabs.find((id) =>
|
||||
editorEntityTypes.table.includes(tabStore.tabsMap[id]?.type)
|
||||
)
|
||||
|
||||
const lastTabId = tabStore.openTabs.find((id) =>
|
||||
editorEntityTypes.table.includes(tabStore.tabsMap[id]?.type)
|
||||
)
|
||||
if (lastOpenedTab !== undefined) {
|
||||
router.push(`/project/${projectRef}/editor/${appSnap.dashboardHistory.editor}`)
|
||||
} else if (lastTabId) {
|
||||
// Handle redirect to last opened table tab, or last table tab
|
||||
const lastTab = tabStore.tabsMap[lastTabId]
|
||||
if (lastTab) router.push(`/project/${projectRef}/editor/${lastTab.metadata?.tableId}`)
|
||||
if (lastOpenedTable !== undefined) {
|
||||
router.push(`/project/${projectRef}/editor/${history.editor}`)
|
||||
} else if (lastTabId) {
|
||||
const lastTab = tabStore.tabsMap[lastTabId]
|
||||
if (lastTab) router.push(`/project/${projectRef}/editor/${lastTab.metadata?.tableId}`)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [isHistoryLoaded])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const HomePage: NextPageWithLayout = () => {
|
||||
const isHomeNew = useFlag('homeNew')
|
||||
|
||||
if (isHomeNew) {
|
||||
return <HomeV2 />
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
@@ -9,8 +10,8 @@ import { useEditorType } from 'components/layouts/editors/EditorsLayout.hooks'
|
||||
import SQLEditorLayout from 'components/layouts/SQLEditorLayout/SQLEditorLayout'
|
||||
import { SQLEditorMenu } from 'components/layouts/SQLEditorLayout/SQLEditorMenu'
|
||||
import { useContentIdQuery } from 'data/content/content-id-query'
|
||||
import Link from 'next/link'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { SnippetWithContent, useSnippets, useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
|
||||
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
@@ -20,11 +21,12 @@ import { Admonition } from 'ui-patterns'
|
||||
const SqlEditor: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { id, ref, content, skip } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
|
||||
const editor = useEditorType()
|
||||
const tabs = useTabsStateSnapshot()
|
||||
const appSnap = useAppStateSnapshot()
|
||||
const snapV2 = useSqlEditorV2StateSnapshot()
|
||||
const { history, setLastVisitedSnippet } = useDashboardHistory()
|
||||
|
||||
const allSnippets = useSnippets(ref!)
|
||||
const snippet = allSnippets.find((x) => x.id === id)
|
||||
@@ -49,21 +51,27 @@ const SqlEditor: NextPageWithLayout = () => {
|
||||
const invalidId = isError && error.code === 400 && error.message.includes('Invalid uuid')
|
||||
|
||||
useEffect(() => {
|
||||
if (ref && data) {
|
||||
snapV2.setSnippet(ref, data as unknown as SnippetWithContent)
|
||||
if (ref && data && project) {
|
||||
// [Joshen] Check if snippet belongs to the current project
|
||||
if (data.project_id === project.id) {
|
||||
snapV2.setSnippet(ref, data as unknown as SnippetWithContent)
|
||||
} else {
|
||||
setLastVisitedSnippet(undefined)
|
||||
router.push(`/project/${ref}/sql/new`)
|
||||
}
|
||||
}
|
||||
}, [ref, data])
|
||||
}, [ref, data, project])
|
||||
|
||||
// Load the last visited snippet when landing on /new
|
||||
useEffect(() => {
|
||||
if (
|
||||
id === 'new' &&
|
||||
skip !== 'true' && // [Joshen] Skip flag implies to skip loading the last visited snippet
|
||||
appSnap.dashboardHistory.sql !== undefined &&
|
||||
history.sql !== undefined &&
|
||||
content === undefined
|
||||
) {
|
||||
const snippet = allSnippets.find((snippet) => snippet.id === appSnap.dashboardHistory.sql)
|
||||
if (snippet !== undefined) router.push(`/project/${ref}/sql/${appSnap.dashboardHistory.sql}`)
|
||||
const snippet = allSnippets.find((snippet) => snippet.id === history.sql)
|
||||
if (snippet !== undefined) router.push(`/project/${ref}/sql/${history.sql}`)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, allSnippets, content])
|
||||
@@ -104,9 +112,7 @@ const SqlEditor: NextPageWithLayout = () => {
|
||||
id: tabId,
|
||||
router,
|
||||
editor,
|
||||
onClearDashboardHistory: () => {
|
||||
if (ref) appSnap.setDashboardHistory(ref, 'sql', undefined)
|
||||
},
|
||||
onClearDashboardHistory: () => setLastVisitedSnippet(undefined),
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,35 +6,39 @@ import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import { EditorBaseLayout } from 'components/layouts/editors/EditorBaseLayout'
|
||||
import SQLEditorLayout from 'components/layouts/SQLEditorLayout/SQLEditorLayout'
|
||||
import { SQLEditorMenu } from 'components/layouts/SQLEditorLayout/SQLEditorMenu'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
|
||||
import { useTabsStateSnapshot } from 'state/tabs'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const TableEditorPage: NextPageWithLayout = () => {
|
||||
const SQLEditorIndexPage: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const store = useTabsStateSnapshot()
|
||||
const appSnap = useAppStateSnapshot()
|
||||
|
||||
const { history, isHistoryLoaded } = useDashboardHistory()
|
||||
|
||||
useEffect(() => {
|
||||
// Handle redirect to last opened snippet tab, or last snippet tab
|
||||
const lastOpenedTab = appSnap.dashboardHistory.sql
|
||||
const lastTabId = store.openTabs.find((id) => store.tabsMap[id]?.type === 'sql')
|
||||
if (lastOpenedTab !== undefined) {
|
||||
router.push(`/project/${projectRef}/sql/${appSnap.dashboardHistory.sql}`)
|
||||
} else if (lastTabId) {
|
||||
const lastTab = store.tabsMap[lastTabId]
|
||||
if (lastTab) router.push(`/project/${projectRef}/sql/${lastTab.id.replace('sql-', '')}`)
|
||||
} else {
|
||||
router.push(`/project/${projectRef}/sql/new`)
|
||||
if (isHistoryLoaded) {
|
||||
// Handle redirect to last opened snippet tab, or last snippet tab
|
||||
const lastOpenedTab = history.sql
|
||||
const lastTabId = store.openTabs.find((id) => store.tabsMap[id]?.type === 'sql')
|
||||
|
||||
if (lastOpenedTab !== undefined) {
|
||||
router.push(`/project/${projectRef}/sql/${history.sql}`)
|
||||
} else if (lastTabId) {
|
||||
const lastTab = store.tabsMap[lastTabId]
|
||||
if (lastTab) router.push(`/project/${projectRef}/sql/${lastTab.id.replace('sql-', '')}`)
|
||||
} else {
|
||||
router.push(`/project/${projectRef}/sql/new`)
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}, [isHistoryLoaded])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
TableEditorPage.getLayout = (page) => (
|
||||
SQLEditorIndexPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<EditorBaseLayout productMenu={<SQLEditorMenu />} product="SQL Editor">
|
||||
<SQLEditorLayout>{page}</SQLEditorLayout>
|
||||
@@ -42,4 +46,4 @@ TableEditorPage.getLayout = (page) => (
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default TableEditorPage
|
||||
export default SQLEditorIndexPage
|
||||
@@ -1,19 +1,9 @@
|
||||
import { proxy, snapshot, useSnapshot } from 'valtio'
|
||||
|
||||
import { LOCAL_STORAGE_KEYS as COMMON_LOCAL_STORAGE_KEYS, LOCAL_STORAGE_KEYS } from 'common'
|
||||
type DashboardHistoryType = {
|
||||
sql?: string
|
||||
editor?: string
|
||||
}
|
||||
|
||||
const EMPTY_DASHBOARD_HISTORY: DashboardHistoryType = {
|
||||
sql: undefined,
|
||||
editor: undefined,
|
||||
}
|
||||
import { LOCAL_STORAGE_KEYS as COMMON_LOCAL_STORAGE_KEYS } from 'common'
|
||||
|
||||
const getInitialState = () => {
|
||||
return {
|
||||
dashboardHistory: EMPTY_DASHBOARD_HISTORY,
|
||||
activeDocsSection: ['introduction'],
|
||||
docsLanguage: 'js',
|
||||
showProjectApiDocs: false,
|
||||
@@ -31,16 +21,6 @@ const getInitialState = () => {
|
||||
export const appState = proxy({
|
||||
...getInitialState(),
|
||||
|
||||
setDashboardHistory: (ref: string, key: 'sql' | 'editor', id: string | undefined) => {
|
||||
if (appState.dashboardHistory[key] !== id) {
|
||||
appState.dashboardHistory[key] = id
|
||||
localStorage.setItem(
|
||||
LOCAL_STORAGE_KEYS.DASHBOARD_HISTORY(ref),
|
||||
JSON.stringify(appState.dashboardHistory)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
activeDocsSection: ['introduction'],
|
||||
docsLanguage: 'js' as 'js' | 'bash',
|
||||
showProjectApiDocs: false,
|
||||
|
||||
Reference in new issue
Block a user