mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Merge pull request #13667 from supabase/chore/fix-table-editor-filter-removing
fix: table editor filter removing
This commit is contained in:
10 files changed
+97
-461
No files matched your search
@@ -152,21 +152,13 @@ const SupabaseGridLayout = forwardRef<SupabaseGridRef, SupabaseGridProps>((props
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.metaService) {
|
||||
dispatch({
|
||||
type: 'INIT_CLIENT',
|
||||
payload: { onSqlQuery: props.onSqlQuery },
|
||||
})
|
||||
dispatch({
|
||||
type: 'INIT_CALLBACK',
|
||||
payload: { ...props },
|
||||
})
|
||||
}
|
||||
}, [state.metaService])
|
||||
dispatch({
|
||||
type: 'INIT_CALLBACK',
|
||||
payload: { ...props },
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.metaService) return
|
||||
|
||||
const initializeData = async () => {
|
||||
const { savedState } = await initTable(
|
||||
props,
|
||||
@@ -176,6 +168,7 @@ const SupabaseGridLayout = forwardRef<SupabaseGridRef, SupabaseGridProps>((props
|
||||
filter as string[]
|
||||
)
|
||||
|
||||
console.log('savedState:', savedState)
|
||||
if (savedState.sorts || savedState.filters) {
|
||||
setParams((prevParams) => {
|
||||
return {
|
||||
@@ -187,17 +180,12 @@ const SupabaseGridLayout = forwardRef<SupabaseGridRef, SupabaseGridProps>((props
|
||||
}
|
||||
}
|
||||
|
||||
const refreshView =
|
||||
typeof props.table === 'string' &&
|
||||
state?.table?.name !== props.table &&
|
||||
state?.table?.schema !== props.schema
|
||||
const refreshTable =
|
||||
typeof props.table !== 'string' && JSON.stringify(props.table) !== JSON.stringify(state.table)
|
||||
const refreshTable = JSON.stringify(props.table) !== JSON.stringify(state.table)
|
||||
|
||||
if (!state.table || refreshView || refreshTable) {
|
||||
if (!state.table || refreshTable) {
|
||||
initializeData()
|
||||
}
|
||||
}, [state.metaService, state.table, props.table, props.schema])
|
||||
}, [state.table, props.table, props.schema])
|
||||
|
||||
return (
|
||||
<div className="sb-grid">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import AwesomeDebouncePromise from 'awesome-debounce-promise'
|
||||
import { STORAGE_KEY_PREFIX } from './constants'
|
||||
import { IMetaService } from './services/meta'
|
||||
import { InitialStateType } from './store/reducers'
|
||||
import { Dictionary, Sort, SupabaseGridProps, SupaColumn, SupaTable } from './types'
|
||||
import { getGridColumns } from './utils/gridColumns'
|
||||
@@ -71,124 +70,52 @@ export async function initTable(
|
||||
props: SupabaseGridProps,
|
||||
state: InitialStateType,
|
||||
dispatch: (value: any) => void,
|
||||
sort?: string[], // Come directly from URL param
|
||||
filter?: string[] // Come directly from URL param
|
||||
sort?: string[], // Comes directly from URL param
|
||||
filter?: string[] // Comes directly from URL param
|
||||
): Promise<{ savedState: { sorts?: string[]; filters?: string[] } }> {
|
||||
function onInitTable(table: SupaTable, props: SupabaseGridProps) {
|
||||
const savedState = props.storageRef
|
||||
? onLoadStorage(props.storageRef, table.name, table.schema)
|
||||
: undefined
|
||||
const savedState = props.storageRef
|
||||
? onLoadStorage(props.storageRef, props.table.name, props.table.schema)
|
||||
: undefined
|
||||
|
||||
// Check for saved state on initial load and also, load sort and filters via URL param only if given
|
||||
// Otherwise load from local storage to resume user session
|
||||
if (
|
||||
!state.isInitialComplete &&
|
||||
sort === undefined &&
|
||||
filter === undefined &&
|
||||
(savedState?.sorts || savedState?.filters)
|
||||
) {
|
||||
return {
|
||||
savedState: {
|
||||
sorts: savedState.sorts,
|
||||
filters: savedState.filters,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const gridColumns = getGridColumns(table, {
|
||||
editable: props.editable,
|
||||
defaultWidth: props.gridProps?.defaultColumnWidth,
|
||||
onAddColumn: props.editable ? props.onAddColumn : undefined,
|
||||
onExpandJSONEditor: props.editable ? props.onExpandJSONEditor : () => {},
|
||||
})
|
||||
|
||||
dispatch({
|
||||
type: 'INIT_TABLE',
|
||||
payload: {
|
||||
table,
|
||||
gridProps: props.gridProps,
|
||||
gridColumns,
|
||||
savedState,
|
||||
editable: props.editable,
|
||||
onSqlQuery: props.onSqlQuery,
|
||||
onError: props.onError ?? defaultErrorHandler,
|
||||
// Check for saved state on initial load and also, load sort and filters via URL param only if given
|
||||
// Otherwise load from local storage to resume user session
|
||||
if (
|
||||
!state.isInitialComplete &&
|
||||
sort === undefined &&
|
||||
filter === undefined &&
|
||||
(savedState?.sorts || savedState?.filters)
|
||||
) {
|
||||
return {
|
||||
savedState: {
|
||||
sorts: savedState.sorts,
|
||||
filters: savedState.filters,
|
||||
},
|
||||
})
|
||||
|
||||
return { savedState: {} }
|
||||
}
|
||||
|
||||
if (typeof props.table === 'string') {
|
||||
const viewData = props.editable
|
||||
? await fetchEditableInfo(state.metaService!, props.table, props.schema)
|
||||
: await fetchReadOnlyInfo(state.metaService!, props.table, props.schema)
|
||||
|
||||
if (viewData) {
|
||||
return onInitTable(viewData, props)
|
||||
} else {
|
||||
if (props.onError) props.onError({ message: 'Failed to fetch data from view' })
|
||||
}
|
||||
} else {
|
||||
return onInitTable(props.table, props)
|
||||
}
|
||||
|
||||
const gridColumns = getGridColumns(props.table, {
|
||||
editable: props.editable,
|
||||
defaultWidth: props.gridProps?.defaultColumnWidth,
|
||||
onAddColumn: props.editable ? props.onAddColumn : undefined,
|
||||
onExpandJSONEditor: props.editable ? props.onExpandJSONEditor : () => {},
|
||||
})
|
||||
|
||||
dispatch({
|
||||
type: 'INIT_TABLE',
|
||||
payload: {
|
||||
table: props.table,
|
||||
gridProps: props.gridProps,
|
||||
gridColumns,
|
||||
savedState,
|
||||
editable: props.editable,
|
||||
onSqlQuery: props.onSqlQuery,
|
||||
onError: props.onError ?? defaultErrorHandler,
|
||||
},
|
||||
})
|
||||
|
||||
return { savedState: {} }
|
||||
}
|
||||
|
||||
async function fetchEditableInfo(
|
||||
service: IMetaService,
|
||||
tableName: string,
|
||||
schema?: string
|
||||
): Promise<SupaTable | null> {
|
||||
const resTable = await service.fetchInfo(tableName, schema)
|
||||
const resColumns = await service.fetchColumns(tableName, schema)
|
||||
const resPrimaryKeys = await service.fetchPrimaryKeys(tableName, schema)
|
||||
const resRelationships = await service.fetchRelationships(tableName, schema)
|
||||
if (
|
||||
resTable.data &&
|
||||
resColumns.data &&
|
||||
resPrimaryKeys.data &&
|
||||
resRelationships.data &&
|
||||
resColumns.data.length > 0
|
||||
) {
|
||||
const supaTable = parseSupaTable({
|
||||
table: resTable.data,
|
||||
columns: resColumns.data,
|
||||
primaryKeys: resPrimaryKeys.data,
|
||||
relationships: resRelationships.data,
|
||||
})
|
||||
return supaTable
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function fetchReadOnlyInfo(
|
||||
service: IMetaService,
|
||||
name: string,
|
||||
schema?: string
|
||||
): Promise<SupaTable | null> {
|
||||
const { data } = await service.fetchColumns(name, schema)
|
||||
|
||||
if (data) {
|
||||
const supaColumns: SupaColumn[] = data.map((x, index) => {
|
||||
return {
|
||||
name: x.name,
|
||||
dataType: x.format,
|
||||
format: x.format,
|
||||
position: index,
|
||||
isUpdatable: false,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
name: name,
|
||||
schema: schema,
|
||||
columns: supaColumns,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function parseSupaTable(
|
||||
data: {
|
||||
table: Dictionary<any>
|
||||
|
||||
@@ -43,10 +43,6 @@ export const Grid = memo(
|
||||
const dispatch = useDispatch()
|
||||
const state = useTrackedState()
|
||||
|
||||
// workaround to force state tracking on state.gridColumns
|
||||
const columnHeaders = state.gridColumns.map((x) => `${x.key}_${x.frozen}`)
|
||||
const { gridColumns, onError: onErrorFunc } = state
|
||||
|
||||
function onColumnResize(index: number, width: number) {
|
||||
updateColumnResizeDebounced(index, width, dispatch)
|
||||
}
|
||||
@@ -110,22 +106,6 @@ export const Grid = memo(
|
||||
}
|
||||
}
|
||||
|
||||
if (!columnHeaders || columnHeaders.length == 0) {
|
||||
return (
|
||||
<div
|
||||
className="sb-grid-grid--loading"
|
||||
style={{ width: width || '100%', height: height || '50vh' }}
|
||||
>
|
||||
<div className="sb-grid-grid--loading__inner flex items-center gap-2">
|
||||
<div className="animate-spin text-scale-900">
|
||||
<IconLoader />
|
||||
</div>
|
||||
<div className="text-sm text-scale-1100">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={containerClass}
|
||||
@@ -133,7 +113,7 @@ export const Grid = memo(
|
||||
>
|
||||
<DataGrid
|
||||
ref={ref}
|
||||
columns={gridColumns}
|
||||
columns={state.gridColumns}
|
||||
rows={rows ?? []}
|
||||
rowRenderer={RowRenderer}
|
||||
rowKeyGetter={rowKeyGetter}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
import { format } from '@scaleleap/pg-format'
|
||||
|
||||
export function getColumnsSql(filter_schema: string, filter_name: string) {
|
||||
return format(columnsSql, filter_schema, filter_name)
|
||||
}
|
||||
const columnsSql = `
|
||||
SELECT
|
||||
c.oid :: int8 AS table_id,
|
||||
nc.nspname AS schema,
|
||||
c.relname AS table,
|
||||
(c.oid || '.' || a.attnum) AS id,
|
||||
a.attnum AS ordinal_position,
|
||||
a.attname AS name,
|
||||
CASE
|
||||
WHEN a.atthasdef THEN pg_get_expr(ad.adbin, ad.adrelid)
|
||||
ELSE NULL
|
||||
END AS default_value,
|
||||
CASE
|
||||
WHEN t.typtype = 'd' THEN CASE
|
||||
WHEN bt.typelem <> 0 :: oid
|
||||
AND bt.typlen = -1 THEN 'ARRAY'
|
||||
WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, NULL)
|
||||
ELSE 'USER-DEFINED'
|
||||
END
|
||||
ELSE CASE
|
||||
WHEN t.typelem <> 0 :: oid
|
||||
AND t.typlen = -1 THEN 'ARRAY'
|
||||
WHEN nt.nspname = 'pg_catalog' THEN format_type(a.atttypid, NULL)
|
||||
ELSE 'USER-DEFINED'
|
||||
END
|
||||
END AS data_type,
|
||||
COALESCE(bt.typname, t.typname) AS format,
|
||||
CASE
|
||||
WHEN a.attidentity IN ('a', 'd') THEN TRUE
|
||||
ELSE FALSE
|
||||
END AS is_identity,
|
||||
CASE
|
||||
a.attidentity
|
||||
WHEN 'a' THEN 'ALWAYS'
|
||||
WHEN 'd' THEN 'BY DEFAULT'
|
||||
ELSE NULL
|
||||
END AS identity_generation,
|
||||
CASE
|
||||
WHEN a.attnotnull
|
||||
OR t.typtype = 'd'
|
||||
AND t.typnotnull THEN FALSE
|
||||
ELSE TRUE
|
||||
END AS is_nullable,
|
||||
CASE
|
||||
WHEN (
|
||||
c.relkind IN ('r', 'p')
|
||||
)
|
||||
OR (
|
||||
c.relkind IN ('v', 'f')
|
||||
)
|
||||
AND pg_column_is_updatable(c.oid, a.attnum, FALSE) THEN TRUE
|
||||
ELSE FALSE
|
||||
END AS is_updatable,
|
||||
array_to_json(
|
||||
array(
|
||||
SELECT
|
||||
enumlabel
|
||||
FROM
|
||||
pg_catalog.pg_enum enums
|
||||
WHERE
|
||||
quote_ident(COALESCE(bt.typname, t.typname)) = format_type(enums.enumtypid, NULL)
|
||||
ORDER BY
|
||||
enums.enumsortorder
|
||||
)
|
||||
) AS enums,
|
||||
col_description(c.oid, a.attnum) AS comment
|
||||
FROM
|
||||
pg_attribute a
|
||||
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid
|
||||
AND a.attnum = ad.adnum
|
||||
JOIN (
|
||||
pg_class c
|
||||
JOIN pg_namespace nc ON c.relnamespace = nc.oid
|
||||
) ON a.attrelid = c.oid
|
||||
JOIN (
|
||||
pg_type t
|
||||
JOIN pg_namespace nt ON t.typnamespace = nt.oid
|
||||
) ON a.atttypid = t.oid
|
||||
LEFT JOIN (
|
||||
pg_type bt
|
||||
JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid
|
||||
) ON t.typtype = 'd'
|
||||
AND t.typbasetype = bt.oid
|
||||
WHERE
|
||||
NOT pg_is_other_temp_schema(nc.oid)
|
||||
AND nc.nspname = %L
|
||||
AND c.relname = %L
|
||||
AND a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND (c.relkind IN ('r', 'v', 'f', 'p'))
|
||||
AND (
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
OR has_column_privilege(
|
||||
c.oid,
|
||||
a.attnum,
|
||||
'SELECT, INSERT, UPDATE, REFERENCES'
|
||||
)
|
||||
)
|
||||
`
|
||||
|
||||
export function getPrimaryKeysSql(filter_schema: string, filter_name: string) {
|
||||
return format(primaryKeysSql, filter_schema, filter_name)
|
||||
}
|
||||
const primaryKeysSql = `
|
||||
SELECT
|
||||
n.nspname AS schema,
|
||||
c.relname AS table_name,
|
||||
a.attname AS name,
|
||||
c.oid :: int8 AS table_id
|
||||
FROM
|
||||
pg_index i,
|
||||
pg_class c,
|
||||
pg_attribute a,
|
||||
pg_namespace n
|
||||
WHERE
|
||||
n.nspname = %L
|
||||
AND c.relname = %L
|
||||
AND i.indrelid = c.oid
|
||||
AND c.relnamespace = n.oid
|
||||
AND a.attrelid = c.oid
|
||||
AND a.attnum = ANY (i.indkey)
|
||||
AND i.indisprimary
|
||||
`
|
||||
|
||||
export function getRelationshipsSql(filter_schema: string, filter_name: string) {
|
||||
return format(relationshipsSql, filter_schema, filter_name)
|
||||
}
|
||||
const relationshipsSql = `
|
||||
SELECT
|
||||
c.oid :: int8 AS id,
|
||||
c.conname AS constraint_name,
|
||||
nsa.nspname AS source_schema,
|
||||
csa.relname AS source_table_name,
|
||||
sa.attname AS source_column_name,
|
||||
nta.nspname AS target_table_schema,
|
||||
cta.relname AS target_table_name,
|
||||
ta.attname AS target_column_name
|
||||
FROM
|
||||
pg_constraint c
|
||||
JOIN (
|
||||
pg_attribute sa
|
||||
JOIN pg_class csa ON sa.attrelid = csa.oid
|
||||
JOIN pg_namespace nsa ON csa.relnamespace = nsa.oid
|
||||
) ON sa.attrelid = c.conrelid
|
||||
AND sa.attnum = ANY (c.conkey)
|
||||
JOIN (
|
||||
pg_attribute ta
|
||||
JOIN pg_class cta ON ta.attrelid = cta.oid
|
||||
JOIN pg_namespace nta ON cta.relnamespace = nta.oid
|
||||
) ON ta.attrelid = c.confrelid
|
||||
AND ta.attnum = ANY (c.confkey)
|
||||
WHERE
|
||||
c.contype = 'f'
|
||||
AND nsa.nspname = %L
|
||||
AND csa.relname = %L;
|
||||
`
|
||||
|
||||
export function getTableSql(filter_schema: string, filter_name: string) {
|
||||
return format(tableSql, filter_schema, filter_name)
|
||||
}
|
||||
const tableSql = `
|
||||
SELECT
|
||||
c.oid :: int8 AS id,
|
||||
nc.nspname AS schema,
|
||||
c.relname AS name,
|
||||
pg_stat_get_live_tuples(c.oid) AS live_rows_estimate,
|
||||
obj_description(c.oid) AS comment
|
||||
FROM
|
||||
pg_namespace nc
|
||||
JOIN pg_class c ON nc.oid = c.relnamespace
|
||||
WHERE
|
||||
nc.nspname = %L
|
||||
AND c.relname = %L
|
||||
AND c.relkind IN ('r', 'p')
|
||||
AND NOT pg_is_other_temp_schema(nc.oid)
|
||||
AND (
|
||||
pg_has_role(c.relowner, 'USAGE')
|
||||
OR has_table_privilege(
|
||||
c.oid,
|
||||
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
|
||||
)
|
||||
OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
`
|
||||
@@ -1,52 +0,0 @@
|
||||
import { IMetaService } from '.'
|
||||
import {
|
||||
getColumnsSql,
|
||||
getPrimaryKeysSql,
|
||||
getRelationshipsSql,
|
||||
getTableSql,
|
||||
} from './SqlMetaService.sqls'
|
||||
|
||||
export class SqlMetaService implements IMetaService {
|
||||
constructor(protected onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>) {}
|
||||
|
||||
async fetchInfo(name: string, schema?: string | undefined) {
|
||||
const sql = getTableSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error }
|
||||
} else {
|
||||
if (data?.length == 1) {
|
||||
return { data: data[0] }
|
||||
} else {
|
||||
return { error: { message: 'fetch table info failed' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
async fetchColumns(name: string, schema?: string | undefined) {
|
||||
const sql = getColumnsSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error }
|
||||
} else {
|
||||
return { data }
|
||||
}
|
||||
}
|
||||
async fetchPrimaryKeys(name: string, schema?: string | undefined) {
|
||||
const sql = getPrimaryKeysSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error }
|
||||
} else {
|
||||
return { data }
|
||||
}
|
||||
}
|
||||
async fetchRelationships(name: string, schema?: string | undefined) {
|
||||
const sql = getRelationshipsSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error }
|
||||
} else {
|
||||
return { data }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Dictionary, ServiceError } from '../../types'
|
||||
|
||||
export interface IMetaService {
|
||||
fetchInfo: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>; error?: ServiceError }>
|
||||
fetchColumns: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
|
||||
fetchPrimaryKeys: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
|
||||
fetchRelationships: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
|
||||
}
|
||||
|
||||
export * from './SqlMetaService'
|
||||
@@ -2,11 +2,9 @@ import { CalculatedColumn } from '@supabase/react-data-grid'
|
||||
import { GridProps, SavedState, SupaTable } from '../../types'
|
||||
import { REFRESH_PAGE_IMMEDIATELY, TOTAL_ROWS_RESET } from '../../constants'
|
||||
import { IRowService, SqlRowService } from '../../services/row'
|
||||
import { IMetaService, SqlMetaService } from '../../services/meta'
|
||||
|
||||
export interface BaseInitialState {
|
||||
table: SupaTable | null
|
||||
metaService: IMetaService | null
|
||||
rowService: IRowService | null
|
||||
refreshPageFlag: number
|
||||
isInitialComplete: boolean
|
||||
@@ -16,7 +14,6 @@ export interface BaseInitialState {
|
||||
|
||||
export const baseInitialState: BaseInitialState = {
|
||||
table: null,
|
||||
metaService: null,
|
||||
rowService: null,
|
||||
refreshPageFlag: 0,
|
||||
isInitialComplete: false,
|
||||
@@ -25,12 +22,6 @@ export const baseInitialState: BaseInitialState = {
|
||||
}
|
||||
|
||||
export type INIT_ACTIONTYPE =
|
||||
| {
|
||||
type: 'INIT_CLIENT'
|
||||
payload: {
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>
|
||||
}
|
||||
}
|
||||
| {
|
||||
type: 'INIT_TABLE'
|
||||
payload: {
|
||||
@@ -56,12 +47,6 @@ type BASE_ACTIONTYPE = INIT_ACTIONTYPE
|
||||
|
||||
const BaseReducer = (state: BaseInitialState, action: BASE_ACTIONTYPE) => {
|
||||
switch (action.type) {
|
||||
case 'INIT_CLIENT': {
|
||||
return {
|
||||
...state,
|
||||
metaService: new SqlMetaService(action.payload.onSqlQuery),
|
||||
}
|
||||
}
|
||||
case 'INIT_TABLE': {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { checkPermissions, useStore } from 'hooks'
|
||||
import { useParams } from 'common/hooks'
|
||||
import { Entity } from 'data/entity-types/entity-type-query'
|
||||
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
|
||||
import { useIsTableLoaded, useTableEditorStateSnapshot } from 'state/table-editor'
|
||||
import ProjectLayout from '../ProjectLayout/ProjectLayout'
|
||||
import TableEditorMenu from './TableEditorMenu'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
@@ -36,9 +37,10 @@ const TableEditorLayout = ({
|
||||
}: PropsWithChildren<TableEditorLayoutProps>) => {
|
||||
const { vault, meta, ui } = useStore()
|
||||
const router = useRouter()
|
||||
const { id: _id } = useParams()
|
||||
const { ref, id: _id } = useParams()
|
||||
const id = _id ? Number(_id) : undefined
|
||||
|
||||
const snap = useTableEditorStateSnapshot()
|
||||
const canReadTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables')
|
||||
|
||||
const vaultExtension = meta.extensions.byId('supabase_vault')
|
||||
@@ -54,15 +56,10 @@ const TableEditorLayout = ({
|
||||
}
|
||||
}, [ui.selectedProject?.ref])
|
||||
|
||||
const [loadedIds, setLoadedIds] = useState<Set<number>>(() => new Set())
|
||||
const isLoaded = id !== undefined && loadedIds.has(id)
|
||||
const isLoaded = useIsTableLoaded(ref, id)
|
||||
|
||||
const entity = useEntityType(id, function onNotFound(id) {
|
||||
setLoadedIds((loadedIds) => {
|
||||
const newLoadedIds = new Set(loadedIds)
|
||||
newLoadedIds.add(id)
|
||||
return newLoadedIds
|
||||
})
|
||||
if (ref) snap.addLoadedId(ref, id)
|
||||
})
|
||||
|
||||
const prefetch = useLatest(useTableRowsPrefetchWrapper())
|
||||
@@ -91,32 +88,23 @@ const TableEditorLayout = ({
|
||||
loadTable()
|
||||
?.then(async (entity: any) => {
|
||||
await prefetch.current(entity)
|
||||
|
||||
return entity
|
||||
})
|
||||
.then((entity: any) => {
|
||||
if (mounted) {
|
||||
setLoadedIds((loadedIds) => {
|
||||
const newLoadedIds = new Set(loadedIds)
|
||||
newLoadedIds.add(entity.id ?? entity?.id)
|
||||
return newLoadedIds
|
||||
})
|
||||
if (mounted && ref) {
|
||||
snap.addLoadedId(ref, entity.id)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (mounted && entity?.id) {
|
||||
setLoadedIds((loadedIds) => {
|
||||
const newLoadedIds = new Set(loadedIds)
|
||||
newLoadedIds.add(entity.id)
|
||||
return newLoadedIds
|
||||
})
|
||||
if (mounted && entity?.id && ref) {
|
||||
snap.addLoadedId(ref, entity.id)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [entity])
|
||||
}, [entity?.id])
|
||||
|
||||
useEffect(() => {
|
||||
if (isVaultEnabled) {
|
||||
|
||||
+12
-9
@@ -26,7 +26,7 @@ import Script from 'next/script'
|
||||
|
||||
import { AppPropsWithLayout } from 'types'
|
||||
import { ThemeProvider } from 'common'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Hydrate, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import { RootStore } from 'stores'
|
||||
@@ -108,15 +108,18 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
|
||||
const getLayout = Component.getLayout ?? ((page) => page)
|
||||
|
||||
const AuthContainer = (props: any) => {
|
||||
return IS_PLATFORM ? (
|
||||
<SessionContextProvider supabaseClient={supabase as any}>
|
||||
const AuthContainer = useMemo(
|
||||
() => (props: any) => {
|
||||
return IS_PLATFORM ? (
|
||||
<SessionContextProvider supabaseClient={supabase as any}>
|
||||
<AuthProvider>{props.children}</AuthProvider>
|
||||
</SessionContextProvider>
|
||||
) : (
|
||||
<AuthProvider>{props.children}</AuthProvider>
|
||||
</SessionContextProvider>
|
||||
) : (
|
||||
<AuthProvider>{props.children}</AuthProvider>
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
[supabase]
|
||||
)
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { proxy, snapshot, useSnapshot } from 'valtio'
|
||||
import { proxySet } from 'valtio/utils'
|
||||
|
||||
export const tableEditorState = proxy({
|
||||
projectRef: undefined as undefined | string,
|
||||
loadedIds: proxySet<number>([]),
|
||||
addLoadedId: (ref: string, id: number) => {
|
||||
if (ref !== tableEditorState.projectRef) {
|
||||
tableEditorState.loadedIds.clear()
|
||||
tableEditorState.projectRef = ref
|
||||
}
|
||||
|
||||
tableEditorState.loadedIds.add(id)
|
||||
},
|
||||
})
|
||||
|
||||
export const getTableEditorStateSnapshot = () => snapshot(tableEditorState)
|
||||
|
||||
export const useTableEditorStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
|
||||
useSnapshot(tableEditorState, options)
|
||||
|
||||
export const useIsTableLoaded = (ref?: string, id?: number) => {
|
||||
const snap = useTableEditorStateSnapshot()
|
||||
|
||||
if (!ref || !id) return false
|
||||
if (ref !== snap.projectRef) return false
|
||||
return snap.loadedIds.has(id)
|
||||
}
|
||||
Reference in new issue
Block a user