mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
377 lines
9.3 KiB
JavaScript
377 lines
9.3 KiB
JavaScript
import { createContext, useContext } from 'react'
|
|
import { makeAutoObservable, toJS } from 'mobx'
|
|
|
|
import { IS_PLATFORM } from 'lib/constants'
|
|
import Tab from './Tab'
|
|
import QueryTab from './QueryTab'
|
|
import Favorite from './Favorite'
|
|
import { SchemasQuery, TableColumnsQuery, AllFunctionsQuery } from './queries'
|
|
import { isUndefined } from 'lodash'
|
|
|
|
let store = null
|
|
let projectRef = null
|
|
let metaProjectRef = null
|
|
export function useSqlEditorStore(ref, meta) {
|
|
if ((ref && ref !== projectRef) || (meta?.projectRef && meta.projectRef !== metaProjectRef)) {
|
|
projectRef = ref
|
|
metaProjectRef = meta.projectRef
|
|
store = new SqlEditorStore(ref, meta)
|
|
}
|
|
|
|
if (!ref || !meta) {
|
|
store = null
|
|
}
|
|
|
|
return store
|
|
}
|
|
|
|
export const TAB_TYPES = {
|
|
WELCOME: 'WELCOME',
|
|
SQL_QUERY: 'SQL_QUERY',
|
|
}
|
|
|
|
export const UTILITY_TAB_TYPES = {
|
|
RESULTS: 'RESULTS',
|
|
}
|
|
|
|
export const SqlEditorContext = createContext(null)
|
|
|
|
export const useSqlStore = () => {
|
|
return useContext(SqlEditorContext)
|
|
}
|
|
|
|
class SqlEditorStore {
|
|
meta
|
|
projectRef
|
|
localStorageKey
|
|
tabs = []
|
|
favorites = []
|
|
selectedTabId
|
|
|
|
keywordCache = []
|
|
schemaCache = []
|
|
tableCache = []
|
|
functionCache = []
|
|
|
|
constructor(projectRef, meta) {
|
|
makeAutoObservable(this, {
|
|
keywordsCache: false,
|
|
schemaCache: false,
|
|
tableCache: false,
|
|
functionCache: false,
|
|
})
|
|
|
|
this.meta = meta
|
|
this.projectRef = projectRef
|
|
this.localStorageKey = `supabase-sql-${projectRef}`
|
|
|
|
this.loadKeywords()
|
|
this.loadSchemas()
|
|
this.loadTablesAndColumns()
|
|
this.loadAllFunctions()
|
|
}
|
|
|
|
get LOCALSTORAGE_SAVED_KEY() {
|
|
return `supabase-queries-state-${this.projectRef}`
|
|
}
|
|
|
|
get activeTab() {
|
|
if (this.tabs.length === 1) {
|
|
this.selectedTabId = this.tabs[0].id
|
|
}
|
|
|
|
const found = this.tabs.find((x) => x.id === this.selectedTabId)
|
|
return found
|
|
}
|
|
|
|
get nextQueryTabName() {
|
|
for (var i = 1; i < 20; i++) {
|
|
const name = `Query-${i}`
|
|
const found = this.tabs.find((x) => x.name.includes(name))
|
|
if (found == undefined) return name
|
|
}
|
|
return `Query`
|
|
}
|
|
|
|
get isActiveQueyTabFavorited() {
|
|
if (this.activeTab.type != TAB_TYPES.SQL_QUERY) return false
|
|
return this.favorites.find((x) => x.key === this.activeTab.id) !== undefined
|
|
}
|
|
|
|
get tabsJson() {
|
|
const json = this.tabs.map((x) => {
|
|
switch (x.type) {
|
|
case TAB_TYPES.WELCOME:
|
|
return { name: x.name, type: x.type }
|
|
case TAB_TYPES.SQL_QUERY:
|
|
return { id: x.id, name: x.name, desc: x.desc, type: x.type, query: x.query }
|
|
default:
|
|
return undefined
|
|
}
|
|
})
|
|
return json
|
|
}
|
|
|
|
get favoriteCards() {
|
|
return this.favorites.filter((x) => !!x.name)
|
|
}
|
|
|
|
get isExecuting() {
|
|
const tab = this.activeTab
|
|
if (tab.type != TAB_TYPES.SQL_QUERY) return false
|
|
else return tab.isExecuting
|
|
}
|
|
|
|
tabsFromContentStore(contentStore, user_id) {
|
|
const snippets = contentStore
|
|
.sqlSnippets((x) => x.owner_id === user_id)
|
|
.map((snippet) => toJS(snippet))
|
|
|
|
// add the welcome tab
|
|
let tabs = IS_PLATFORM ? [new Tab('Welcome', TAB_TYPES.WELCOME)] : []
|
|
|
|
// add the tabs to array, but with structure the localStore expects
|
|
snippets.forEach((snippet) => {
|
|
const data = {
|
|
desc: snippet.description,
|
|
id: snippet.id,
|
|
name: snippet.name,
|
|
query: snippet.content.sql,
|
|
type: 'SQL_QUERY',
|
|
favorite: snippet.content.favorite,
|
|
}
|
|
|
|
tabs.push(data)
|
|
})
|
|
|
|
return tabs
|
|
}
|
|
|
|
/*
|
|
* ! Temporary solution !
|
|
*
|
|
* New action for retriving data from main db user_content
|
|
* You need to pass the contentStore into this
|
|
*/
|
|
async loadRemotePersistentData(contentStore, user_id) {
|
|
await contentStore.load()
|
|
|
|
const tabs = this.tabsFromContentStore(contentStore, user_id)
|
|
|
|
/*
|
|
* Reshape snippet content to fit the SqlEditorStore shape
|
|
*/
|
|
const payload = {
|
|
version: '1.0',
|
|
tabs: [...tabs],
|
|
favorites: [],
|
|
}
|
|
|
|
/*
|
|
* Run loadPersistentData() as normal, although now with remote data
|
|
*/
|
|
this.loadPersistentData(payload)
|
|
}
|
|
|
|
loadPersistentData(json) {
|
|
if (!json) {
|
|
this.loadInitialData()
|
|
return
|
|
}
|
|
|
|
if ('version' in json) {
|
|
this.loadTabs(json.tabs || [])
|
|
} else if ('results' in json) {
|
|
// legacy saved data
|
|
this.loadLegacyTabs(json.results || [])
|
|
}
|
|
|
|
this.loadFavorites(json.favorites || [])
|
|
|
|
this.loadPreviousSession()
|
|
}
|
|
|
|
loadInitialData() {
|
|
this.tabs = [
|
|
new Tab('Welcome', TAB_TYPES.WELCOME),
|
|
// new Tab('scripts', TAB_TYPES.WELCOME),
|
|
// new Tab('quickstarts', TAB_TYPES.WELCOME),
|
|
new QueryTab(this.nextQueryTabName, TAB_TYPES.SQL_QUERY),
|
|
]
|
|
this.selectedTabId = this.tabs[0].id
|
|
}
|
|
|
|
// for previous sql editor saved data
|
|
loadLegacyTabs(values) {
|
|
let tabs = [new Tab('Welcome', TAB_TYPES.WELCOME)]
|
|
tabs = tabs.concat(
|
|
values.map((x, index) => {
|
|
const tab = new QueryTab(this.meta, `Query-${index + 1}`, TAB_TYPES.SQL_QUERY)
|
|
tab.id = x.key
|
|
tab.query = x.query
|
|
return tab
|
|
})
|
|
)
|
|
this.tabs = tabs
|
|
this.selectedTabId = this.tabs.length ? this.tabs[0].id : undefined
|
|
}
|
|
|
|
loadTabs(values, autoSelectTab = true) {
|
|
const tabs = values.map((x) => {
|
|
switch (x.type) {
|
|
case TAB_TYPES.WELCOME:
|
|
return new Tab(x.name, x.type)
|
|
case TAB_TYPES.SQL_QUERY:
|
|
const tab = new QueryTab(this.meta, x.name, x.type, x.desc, x.favorite || false)
|
|
tab.id = x.id
|
|
tab.query = x.query
|
|
return tab
|
|
default:
|
|
return undefined
|
|
}
|
|
})
|
|
|
|
this.tabs = tabs.filter((x) => x !== undefined)
|
|
|
|
if (autoSelectTab) {
|
|
this.selectedTabId = this.tabs.length ? this.tabs[0].id : undefined
|
|
}
|
|
}
|
|
|
|
loadFavorites(values) {
|
|
this.favorites = values.map((x) => new Favorite(x.key, x.query, x.name, x.desc))
|
|
}
|
|
|
|
loadPreviousSession() {
|
|
const tabId = localStorage.getItem(this.localStorageKey)
|
|
const allSnippets = this.tabs.concat(this.favorites)
|
|
const selectedTab = allSnippets.find((tab) => tab.id === tabId)
|
|
|
|
if (!isUndefined(selectedTab)) {
|
|
this.selectTab(selectedTab.id)
|
|
}
|
|
}
|
|
|
|
async loadKeywords() {
|
|
const query = 'select * from pg_get_keywords();'
|
|
const response = await this.meta.query(query)
|
|
if (response && !response.error) {
|
|
this.keywordCache = response.map((x) => x.word.toLocaleLowerCase())
|
|
}
|
|
}
|
|
|
|
async loadSchemas() {
|
|
const response = await this.meta.query(SchemasQuery)
|
|
if (!response.error) {
|
|
this.schemaCache = response
|
|
}
|
|
}
|
|
|
|
async loadTablesAndColumns() {
|
|
const response = await this.meta.query(TableColumnsQuery)
|
|
if (!response.error) {
|
|
this.tableCache = response
|
|
}
|
|
}
|
|
|
|
async loadAllFunctions() {
|
|
const response = await this.meta.query(AllFunctionsQuery)
|
|
if (!response.error) {
|
|
this.functionCache = response.map((x) => {
|
|
const args = x.argument_types
|
|
.split(',')
|
|
.filter((a) => a)
|
|
.map((a) => a.trim())
|
|
return { ...x, args }
|
|
})
|
|
}
|
|
}
|
|
|
|
selectTab(id) {
|
|
// Save selected tab in local storage
|
|
localStorage.setItem(this.localStorageKey, id)
|
|
|
|
// reset new selected query tab
|
|
const found = this.tabs.find((x) => x.id === this.selectedTabId)
|
|
if (found && found.type == TAB_TYPES.SQL_QUERY) {
|
|
found.isExecuting = false
|
|
}
|
|
|
|
this.selectedTabId = id
|
|
}
|
|
|
|
closeTab(id) {
|
|
const tabs = this.tabs.filter((x) => x.id !== id)
|
|
// if user close selectedTab, select the last tab if available
|
|
if (tabs.length && this.selectedTabId === id) {
|
|
const nextId = tabs[tabs.length - 1].id
|
|
|
|
if (nextId) {
|
|
this.selectedTabId = nextId
|
|
}
|
|
}
|
|
}
|
|
|
|
createQueryTab(query, name) {
|
|
const newTab = new QueryTab(this.meta, name ? name : this.nextQueryTabName, TAB_TYPES.SQL_QUERY)
|
|
newTab.setQuery(query)
|
|
|
|
this.tabs.push(newTab)
|
|
this.selectedTabId = newTab.id
|
|
}
|
|
|
|
createQueryTabFromFavorite(key) {
|
|
const foundTab = this.tabs.find((x) => x.id == key)
|
|
if (foundTab) {
|
|
this.selectedTabId = foundTab.id
|
|
return
|
|
}
|
|
|
|
const foundFav = this.favorites.find((x) => x.key == key)
|
|
if (foundFav) {
|
|
const newTab = new QueryTab(this.meta, foundFav.name, TAB_TYPES.SQL_QUERY, foundFav.desc)
|
|
newTab.id = foundFav.key
|
|
newTab.query = foundFav.query
|
|
|
|
this.tabs.push(newTab)
|
|
this.selectedTabId = newTab.id
|
|
}
|
|
}
|
|
|
|
reorderTabs(startIndex, endIndex) {
|
|
const result = Array.from(this.tabs)
|
|
const [removed] = result.splice(startIndex, 1)
|
|
result.splice(endIndex, 0, removed)
|
|
this.tabs = result
|
|
}
|
|
|
|
addToFavorite(key, query, name, desc) {
|
|
const found = this.favorites.find((x) => x.key === key)
|
|
if (!found) {
|
|
const newFav = new Favorite(key, query, name, desc)
|
|
this.favorites = [newFav, ...this.favorites]
|
|
}
|
|
}
|
|
|
|
unFavorite(key) {
|
|
this.favorites = this.favorites.filter((x) => x.key != key)
|
|
}
|
|
|
|
renameQuery(id, model) {
|
|
const found = this.tabs.find((x) => x.id === id)
|
|
found?.rename(model)
|
|
|
|
const favorite = this.favorites.find((x) => x.key === id)
|
|
favorite?.rename(model)
|
|
}
|
|
|
|
async startExecuting(query) {
|
|
const tab = this.activeTab
|
|
if (!tab || tab.type != TAB_TYPES.SQL_QUERY) return
|
|
|
|
await tab.startExecuting(query)
|
|
}
|
|
}
|
|
|
|
export default SqlEditorStore
|