import type { PostgresColumn } from '@supabase/postgres-meta' import { PropsWithChildren, createContext, useContext } from 'react' import { proxy, useSnapshot } from 'valtio' import { useConstant } from 'common' import type { SupaRow } from 'components/grid/types' import { ForeignKey } from 'components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.types' import type { EditValue } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types' import type { TableField } from 'components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types' import type { Dictionary } from 'types' export const TABLE_EDITOR_DEFAULT_ROWS_PER_PAGE = 100 type ForeignKeyState = { foreignKey: ForeignKey row: Dictionary column: PostgresColumn } export type SidePanel = | { type: 'cell'; value?: { column: string; row: Dictionary } } | { type: 'row'; row?: Dictionary } | { type: 'column'; column?: PostgresColumn } | { type: 'table'; mode: 'new' | 'edit' | 'duplicate'; templateData?: Partial } | { type: 'schema'; mode: 'new' | 'edit' } | { type: 'json'; jsonValue: EditValue } | { type: 'foreign-row-selector' foreignKey: ForeignKeyState } | { type: 'csv-import'; file?: File } export type ConfirmationDialog = | { type: 'table'; isDeleteWithCascade: boolean } | { type: 'column'; column: PostgresColumn; isDeleteWithCascade: boolean } // [Joshen] Just FYI callback, numRows, allRowsSelected is a temp workaround so that // DeleteConfirmationDialog can trigger dispatch methods after the successful deletion of rows. // Once we deprecate react tracked and move things to valtio, we can remove this. | { type: 'row' rows: SupaRow[] numRows?: number allRowsSelected?: boolean callback?: () => void } export type UIState = | { open: 'none' } | { open: 'side-panel' sidePanel: SidePanel } | { open: 'confirmation-dialog' confirmationDialog: ConfirmationDialog } /** * Global table editor state for the table editor across multiple tables. * See ./table-editor-table.tsx for table specific state. */ export const createTableEditorState = () => { const state = proxy({ rowsPerPage: TABLE_EDITOR_DEFAULT_ROWS_PER_PAGE, setRowsPerPage: (rowsPerPage: number) => { state.rowsPerPage = rowsPerPage }, ui: { open: 'none' } as UIState, get sidePanel() { return state.ui.open === 'side-panel' ? state.ui.sidePanel : undefined }, get confirmationDialog() { return state.ui.open === 'confirmation-dialog' ? state.ui.confirmationDialog : undefined }, closeSidePanel: () => { state.ui = { open: 'none' } }, closeConfirmationDialog: () => { state.ui = { open: 'none' } }, onAddSchema: () => { state.ui = { open: 'side-panel', sidePanel: { type: 'schema', mode: 'new' }, } }, /* Tables */ onAddTable: (templateData?: Partial) => { state.ui = { open: 'side-panel', sidePanel: { type: 'table', mode: 'new', templateData }, } }, onEditTable: () => { state.ui = { open: 'side-panel', sidePanel: { type: 'table', mode: 'edit' }, } }, onDuplicateTable: () => { state.ui = { open: 'side-panel', sidePanel: { type: 'table', mode: 'duplicate' }, } }, onDeleteTable: () => { state.ui = { open: 'confirmation-dialog', confirmationDialog: { type: 'table', isDeleteWithCascade: false }, } }, /* Columns */ onAddColumn: () => { state.ui = { open: 'side-panel', sidePanel: { type: 'column' }, } }, onEditColumn: (column: PostgresColumn) => { state.ui = { open: 'side-panel', sidePanel: { type: 'column', column }, } }, onDeleteColumn: (column: PostgresColumn) => { state.ui = { open: 'confirmation-dialog', confirmationDialog: { type: 'column', column, isDeleteWithCascade: false }, } }, /* Rows */ onAddRow: () => { state.ui = { open: 'side-panel', sidePanel: { type: 'row' }, } }, onEditRow: (row: Dictionary) => { state.ui = { open: 'side-panel', sidePanel: { type: 'row', row }, } }, onDeleteRows: ( rows: SupaRow[], meta: { numRows?: number; allRowsSelected: boolean; callback?: () => void } = { numRows: 0, allRowsSelected: false, callback: () => {}, } ) => { const { numRows, allRowsSelected, callback } = meta state.ui = { open: 'confirmation-dialog', confirmationDialog: { type: 'row', rows, numRows, allRowsSelected, callback }, } }, /* Misc */ onExpandJSONEditor: (jsonValue: EditValue) => { state.ui = { open: 'side-panel', sidePanel: { type: 'json', jsonValue }, } }, onExpandTextEditor: (column: string, row: Dictionary) => { state.ui = { open: 'side-panel', sidePanel: { type: 'cell', value: { column, row } }, } }, onEditForeignKeyColumnValue: (foreignKey: ForeignKeyState) => { state.ui = { open: 'side-panel', sidePanel: { type: 'foreign-row-selector', foreignKey }, } }, onImportData: (file?: File) => { state.ui = { open: 'side-panel', sidePanel: { type: 'csv-import', file }, } }, /* Utils */ toggleConfirmationIsWithCascade: (overrideIsDeleteWithCascade?: boolean) => { if ( state.ui.open === 'confirmation-dialog' && (state.ui.confirmationDialog.type === 'column' || state.ui.confirmationDialog.type === 'table') ) { state.ui.confirmationDialog.isDeleteWithCascade = overrideIsDeleteWithCascade ?? !state.ui.confirmationDialog.isDeleteWithCascade } }, }) return state } export type TableEditorState = ReturnType export const TableEditorStateContext = createContext(createTableEditorState()) export const TableEditorStateContextProvider = ({ children }: PropsWithChildren<{}>) => { const state = useConstant(createTableEditorState) return ( {children} ) } export const useTableEditorStateSnapshot = (options?: Parameters[1]) => { const state = useContext(TableEditorStateContext) return useSnapshot(state, options) }