mirror of
https://github.com/supabase/supabase.git
synced 2026-06-20 21:16:00 +08:00
* fix * fix type issues * fix type err * fix type error 2 the return of the type * fix type error 3 tokyo drift
244 lines
7.5 KiB
TypeScript
244 lines
7.5 KiB
TypeScript
import { debounce, memoize } from 'lodash'
|
|
import { useMemo } from 'react'
|
|
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'
|
|
import { devtools, proxySet } from 'valtio/utils'
|
|
|
|
import { upsertContent, UpsertContentPayload } from 'data/content/content-upsert-mutation'
|
|
import type { SqlSnippet } from 'data/content/sql-snippets-query'
|
|
import type { SqlSnippets } from 'types'
|
|
|
|
export type StateSnippet = {
|
|
snippet: SqlSnippet
|
|
splitSizes: number[]
|
|
projectRef: string
|
|
}
|
|
|
|
export const sqlEditorState = proxy({
|
|
snippets: {} as {
|
|
[key: string]: StateSnippet
|
|
},
|
|
results: {} as {
|
|
[key: string]: {
|
|
rows: any[]
|
|
error?: any
|
|
autoLimit?: number
|
|
}[]
|
|
},
|
|
// Project ref as the key, ids of each snippet as the order
|
|
orders: {} as {
|
|
[key: string]: string[]
|
|
},
|
|
loaded: {} as {
|
|
[key: string]: boolean
|
|
},
|
|
limit: 100,
|
|
|
|
needsSaving: proxySet<string>([]),
|
|
savingStates: {} as {
|
|
[key: string]: 'IDLE' | 'UPDATING' | 'UPDATING_FAILED'
|
|
},
|
|
|
|
setLimit: (value: number) => (sqlEditorState.limit = value),
|
|
orderSnippets: (snippets: SqlSnippet[]) => {
|
|
return (
|
|
snippets
|
|
.filter((s) => Boolean(s.id))
|
|
// first alphabetical
|
|
.sort((a, b) => a.name?.localeCompare(b.name))
|
|
)
|
|
},
|
|
reorderSnippets: (projectRef: string) => {
|
|
sqlEditorState.orders[projectRef] = sqlEditorState
|
|
.orderSnippets(
|
|
sqlEditorState.orders[projectRef].map((id) => sqlEditorState.snippets[id].snippet)
|
|
)
|
|
.map((s) => s.id!)
|
|
},
|
|
|
|
setRemoteSnippets: (snippets: SqlSnippet[], projectRef: string) => {
|
|
if (!sqlEditorState.orders[projectRef]) {
|
|
const orderedSnippets = sqlEditorState.orderSnippets(snippets)
|
|
|
|
sqlEditorState.orders[projectRef] = orderedSnippets.map((s) => s.id!)
|
|
}
|
|
|
|
snippets.forEach((snippet) => {
|
|
sqlEditorState.addSnippet(snippet, projectRef)
|
|
})
|
|
},
|
|
addSnippet: (snippet: SqlSnippet, projectRef: string) => {
|
|
if (snippet.id && !sqlEditorState.snippets[snippet.id]) {
|
|
sqlEditorState.snippets[snippet.id] = {
|
|
snippet,
|
|
splitSizes: [50, 50],
|
|
projectRef,
|
|
}
|
|
sqlEditorState.results[snippet.id] = []
|
|
sqlEditorState.savingStates[snippet.id] = 'IDLE'
|
|
if (
|
|
sqlEditorState.orders[projectRef] !== undefined &&
|
|
!sqlEditorState.orders[projectRef].includes(snippet.id)
|
|
) {
|
|
sqlEditorState.orders[projectRef].unshift(snippet.id)
|
|
sqlEditorState.reorderSnippets(projectRef)
|
|
}
|
|
}
|
|
sqlEditorState.loaded[projectRef] = true
|
|
},
|
|
removeSnippet: (id: string) => {
|
|
const { [id]: snippet, ...otherSnippets } = sqlEditorState.snippets
|
|
sqlEditorState.snippets = otherSnippets
|
|
|
|
const { [id]: result, ...otherResults } = sqlEditorState.results
|
|
sqlEditorState.results = otherResults
|
|
|
|
sqlEditorState.orders[snippet.projectRef] = sqlEditorState.orders[snippet.projectRef].filter(
|
|
(s) => s !== id
|
|
)
|
|
|
|
sqlEditorState.needsSaving.delete(id)
|
|
},
|
|
updateSnippet: (id: string, snippet: SqlSnippet) => {
|
|
if (sqlEditorState.snippets[id]) {
|
|
sqlEditorState.snippets[id].snippet = snippet
|
|
sqlEditorState.needsSaving.add(id)
|
|
}
|
|
},
|
|
setSplitSizes: (id: string, splitSizes: number[]) => {
|
|
if (sqlEditorState.snippets[id]) {
|
|
sqlEditorState.snippets[id].splitSizes = splitSizes
|
|
}
|
|
},
|
|
collapseUtilityPanel: (id: string) => {
|
|
if (sqlEditorState.snippets[id]) {
|
|
sqlEditorState.snippets[id].splitSizes = [100, 0]
|
|
}
|
|
},
|
|
restoreUtilityPanel: (id: string) => {
|
|
if (sqlEditorState.snippets[id]) {
|
|
sqlEditorState.snippets[id].splitSizes = [50, 50]
|
|
}
|
|
},
|
|
setSql: (id: string, sql: string) => {
|
|
if (sqlEditorState.snippets[id] && sqlEditorState.snippets[id].snippet.type === 'sql') {
|
|
sqlEditorState.snippets[id].snippet.content.sql = sql
|
|
sqlEditorState.needsSaving.add(id)
|
|
}
|
|
},
|
|
renameSnippet: (id: string, name: string, description?: string) => {
|
|
if (sqlEditorState.snippets[id]) {
|
|
const { snippet, projectRef } = sqlEditorState.snippets[id]
|
|
|
|
snippet.name = name
|
|
snippet.description = description
|
|
|
|
sqlEditorState.reorderSnippets(projectRef)
|
|
sqlEditorState.needsSaving.add(id)
|
|
}
|
|
},
|
|
shareSnippet: (id: string, visibility: 'user' | 'project' | 'org' | 'public') => {
|
|
if (sqlEditorState.snippets[id]) {
|
|
const { snippet, projectRef } = sqlEditorState.snippets[id]
|
|
|
|
snippet.visibility = visibility
|
|
|
|
sqlEditorState.reorderSnippets(projectRef)
|
|
sqlEditorState.needsSaving.add(id)
|
|
}
|
|
},
|
|
addNeedsSaving: (id: string) => {
|
|
sqlEditorState.needsSaving.add(id)
|
|
},
|
|
resetResult: (id: string) => {
|
|
if (sqlEditorState.results[id]) {
|
|
sqlEditorState.results[id] = []
|
|
}
|
|
},
|
|
addResult: (id: string, results: any[], autoLimit?: number) => {
|
|
if (sqlEditorState.results[id]) {
|
|
sqlEditorState.results[id].unshift({ rows: results, autoLimit })
|
|
}
|
|
},
|
|
addResultError: (id: string, error: any, autoLimit?: number) => {
|
|
if (sqlEditorState.results[id]) {
|
|
sqlEditorState.results[id].unshift({ rows: [], error, autoLimit })
|
|
}
|
|
},
|
|
// [Jordi] Only SQL snippets can be favorites for now.
|
|
addFavorite: (id: string) => {
|
|
if (sqlEditorState.snippets[id] && sqlEditorState.snippets[id].snippet.type === 'sql') {
|
|
sqlEditorState.snippets[id].snippet.content.favorite = true
|
|
sqlEditorState.needsSaving.add(id)
|
|
}
|
|
},
|
|
removeFavorite: (id: string) => {
|
|
if (sqlEditorState.snippets[id] && sqlEditorState.snippets[id].snippet.type === 'sql') {
|
|
sqlEditorState.snippets[id].snippet.content.favorite = false
|
|
sqlEditorState.needsSaving.add(id)
|
|
}
|
|
},
|
|
})
|
|
|
|
export const getSqlEditorStateSnapshot = () => snapshot(sqlEditorState)
|
|
|
|
export const useSqlEditorStateSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
|
|
useSnapshot(sqlEditorState, options)
|
|
|
|
export const useSnippets = (projectRef: string | undefined) => {
|
|
const snapshot = useSqlEditorStateSnapshot()
|
|
|
|
return useMemo(() => {
|
|
return projectRef
|
|
? snapshot.orders[projectRef]?.map((id) => snapshot.snippets[id].snippet) ?? []
|
|
: []
|
|
}, [projectRef, snapshot.orders, snapshot.snippets])
|
|
}
|
|
|
|
async function upsert(id: string, projectRef: string, payload: UpsertContentPayload) {
|
|
try {
|
|
sqlEditorState.savingStates[id] = 'UPDATING'
|
|
await upsertContent({
|
|
projectRef,
|
|
payload,
|
|
})
|
|
sqlEditorState.savingStates[id] = 'IDLE'
|
|
} catch (error) {
|
|
sqlEditorState.savingStates[id] = 'UPDATING_FAILED'
|
|
}
|
|
}
|
|
|
|
const memoizedUpdate = memoize((_id: string) => debounce(upsert, 1000))
|
|
const debouncedUpdate = (id: string, projectRef: string, payload: UpsertContentPayload) =>
|
|
memoizedUpdate(id)(id, projectRef, payload)
|
|
|
|
if (typeof window !== 'undefined') {
|
|
devtools(sqlEditorState, {
|
|
name: 'sqlEditorState',
|
|
// [Joshen] So that jest unit tests can ignore this
|
|
enabled: process.env.NEXT_PUBLIC_ENVIRONMENT !== undefined,
|
|
})
|
|
|
|
subscribe(sqlEditorState.needsSaving, () => {
|
|
const state = getSqlEditorStateSnapshot()
|
|
|
|
Array.from(state.needsSaving).forEach((id) => {
|
|
const snippet = state.snippets[id]
|
|
|
|
if (snippet) {
|
|
debouncedUpdate(id, snippet.projectRef, {
|
|
...snippet.snippet,
|
|
name: snippet.snippet.name ?? 'Untitled',
|
|
description: snippet.snippet.description ?? '',
|
|
visibility: snippet.snippet.visibility ?? 'user',
|
|
project_id: snippet.snippet.project_id ?? 0,
|
|
content: { ...snippet.snippet.content, content_id: id } as SqlSnippets.Content,
|
|
type: 'sql',
|
|
id,
|
|
})
|
|
|
|
sqlEditorState.needsSaving.delete(id)
|
|
}
|
|
})
|
|
})
|
|
}
|