<script setup>
import 'devdb-ui/style.css'
import './assets/base.css'
import './assets/style.css'
import { DevDB } from 'devdb-ui'
import { RouterView } from 'vue-router'
import { onMounted, ref } from 'vue'
const vscode = ref()
const message = ref('')
const providers = ref([])
const connected = ref(false)
const tables = ref([])
const displayedTabs = ref([])
const activeTabIndex = ref()
const userPreferences = ref({})
const editSession = ref([])
const mcpServerConfig = ref([])
const itemsPerPage = ref(10)
const remoteConnections = ref([])
const connectingRemoteId = ref(undefined)
const loadingTableNames = ref([])
const loadingTableData = ref(false)
const silentlyRefreshingTab = ref(false)
const hasLicense = ref(false)
const devDb = ref(null)
onMounted(() => {
vscode.value = acquireVsCodeApi()
setupEventHandlers()
vscode.value.postMessage({ type: 'request:get-user-preferences' })
vscode.value.postMessage({ type: 'request:get-available-providers' })
vscode.value.postMessage({ type: 'request:get-mcp-config' })
vscode.value.postMessage({ type: 'request:get-remote-connections' })
vscode.value.postMessage({ type: 'request:get-license-status' })
})
function setupEventHandlers() {
window.addEventListener('message', event => {
const payload = event.data
switch (payload.type) {
case 'response:get-mcp-config':
mcpServerConfig.value = payload.value
break
case 'response:get-user-preferences':
userPreferences.value = payload.value
break
case 'response:get-available-providers':
providers.value = payload.value
break
case 'response:select-provider':
case 'response:select-provider-option':
const successfullySelected = payload.value
if (successfullySelected) {
connected.value = true
vscode.value.postMessage({ type: 'request:get-tables' })
}
break
case 'response:get-tables':
if (isReconnection()) {
notify('Reconnected')
}
tables.value = payload.value
break
case 'response:get-fresh-table-data':
if (payload.value?.table) loadingTableNames.value = loadingTableNames.value.filter(t => t !== payload.value.table)
loadingTableData.value = false
const tab = buildTabFromPayload(payload)
if (!tab) return
displayedTabs.value.push(tab)
activeTabIndex.value = displayedTabs.value.length - 1
break
case 'response:get-refreshed-table-data':
case 'response:load-table-into-current-tab':
if (payload.value?.table) loadingTableNames.value = loadingTableNames.value.filter(t => t !== payload.value.table)
loadingTableData.value = false
console.log({ 'refresh-response-payload': payload }) // TODO: ran into a situation where refreshing a tab didn't reflect latest db changes. remove this line when the issue is fixed
const alternativeTab = buildTabFromPayload(payload)
if (!alternativeTab) return
displayedTabs.value.splice(activeTabIndex.value, 1, alternativeTab)
break
case 'response:get-filtered-table-data':
loadingTableData.value = false
const updatedTab = buildTabFromPayload(payload)
if (!updatedTab) return
displayedTabs.value[activeTabIndex.value] = updatedTab
break
case 'response:get-data-for-tab-page':
loadingTableData.value = false
silentlyRefreshingTab.value = false
if (!payload.value) return
const currentTab = displayedTabs.value[activeTabIndex.value]
const dataChanged = JSON.stringify(currentTab.rows) !== JSON.stringify(payload.value.rows)
if (dataChanged) {
currentTab.lastQuery = payload.value.lastQuery
currentTab.rows = payload.value.rows
currentTab.pagination = payload.value.pagination
}
break
case 'ide-action:show-table-data':
getFreshTableData(payload.value, itemsPerPage.value)
break
case 'config-changed':
userPreferences.value = payload.value
break
case 'response:write-mutations':
editSession.value = editSession.value.map(session => {
if (session.tabId == payload.value.tabId) {
session.outcome = payload.value.outcome
session.errorMessage = payload.value.errorMessage
if (session.outcome === 'success') {
refreshTabById(payload) // also ensure changes from other tabs are reflected
}
}
return session
})
break
case 'response:reconnect':
if (payload) {
message.value = 'Reconnected'
}
break
case 'response:get-license-status':
hasLicense.value = payload.value?.hasLicense ?? false
break
case 'response:get-remote-connections':
case 'response:save-remote-connection':
case 'response:delete-remote-connection':
remoteConnections.value = payload.value || []
break
case 'response:get-remote-connection':
if (payload.value) {
devDb.value?.openEditDialog(payload.value)
}
break
case 'response:test-remote-connection':
devDb.value?.setTestConnectionResult(payload.value)
break
case 'response:connect-to-remote':
connectingRemoteId.value = undefined
if (payload.value?.connected) {
connected.value = true
vscode.value.postMessage({ type: 'request:get-tables' })
} else if (payload.value?.error) {
notify(payload.value.error)
}
break
}
})
}
function buildTabFromPayload(payload) {
if (!payload.value) return
const tab = {
id: payload.value.id,
table: payload.value.table,
filters: payload.value.filters || {},
columns: payload.value.columns,
rows: payload.value.rows,
totalRows: payload.value.totalRows,
pagination: payload.value.pagination,
tableCreationSql: payload.value.tableCreationSql,
lastQuery: payload.value.lastQuery,
}
return tab
}
function selectProvider(id) {
vscode.value.postMessage({ type: 'request:select-provider', value: id })
}
function selectProviderOption(option) {
vscode.value.postMessage({ type: 'request:select-provider-option', value: removeProxyWrap(option) })
}
function refreshProviders() {
vscode.value.postMessage({ type: 'request:get-available-providers' })
}
function removeTab(tabIndex) {
displayedTabs.value.splice(tabIndex, 1)
if (activeTabIndex.value !== undefined && tabIndex < activeTabIndex.value) {
activeTabIndex.value--
}
}
function destroyUi() {
tables.value = []
displayedTabs.value = []
connected.value = false
}
function reconnect() {
vscode.value.postMessage({ type: 'request:reconnect' })
}
function isReconnection() {
return tables.value.length || displayedTabs.value.length
}
// JSON strip this so we prevent "[object Object] could not be cloned" error
function removeProxyWrap(value) {
return JSON.parse(JSON.stringify(value))
}
function getFreshTableData(table, itemsPerPage) {
loadingTableNames.value = [...loadingTableNames.value, table]
vscode.value.postMessage({ type: 'request:get-fresh-table-data', value: { table, itemsPerPage } })
}
function refreshActiveTab(activeTab) {
loadingTableData.value = true
vscode.value.postMessage({
type: 'request:get-refreshed-table-data',
value: {
table: activeTab.table,
itemsPerPage: activeTab.pagination.itemsPerPage,
},
})
}
function loadTableIntoCurrentTab(table) {
loadingTableNames.value = [...loadingTableNames.value, table]
loadingTableData.value = true
if (activeTabIndex.value === undefined || activeTabIndex.value === null) {
return getFreshTableData(table, itemsPerPage.value)
}
vscode.value.postMessage({
type: 'request:load-table-into-current-tab',
value: {
table: table,
itemsPerPage: itemsPerPage.value,
},
})
}
function getFilteredData(filters, itemsPerPage) {
if (activeTabIndex.value === undefined || activeTabIndex.value === null) return
displayedTabs.value[activeTabIndex.value].filters = filters
displayedTabs.value[activeTabIndex.value].itemsPerPage = itemsPerPage
const tab = displayedTabs.value[activeTabIndex.value]
updateCurrentTabFilter(tab.table, itemsPerPage, filters)
}
function updateCurrentTabFilter(table, itemsPerPage, filters) {
loadingTableData.value = true
filters = filters ? removeProxyWrap(filters) : null
vscode.value.postMessage({ type: 'request:get-filtered-table-data', value: { table, itemsPerPage, filters } })
}
function switchToTab(tabIndex) {
activeTabIndex.value = tabIndex
const tab = displayedTabs.value[tabIndex]
silentRefreshTab(tab)
}
function silentRefreshTab(tab) {
silentlyRefreshingTab.value = true
tab = removeProxyWrap(tab)
vscode.value.postMessage({
type: 'request:get-data-for-tab-page',
value: {
table: tab.table,
columns: tab.columns,
page: tab.pagination.currentPage,
whereClause: tab.filters,
totalRows: tab.totalRows,
itemsPerPage: tab.pagination.itemsPerPage,
},
})
}
function getDataForTabPage(tab, page) {
loadingTableData.value = true
tab = removeProxyWrap(tab)
vscode.value.postMessage({
type: 'request:get-data-for-tab-page',
value: {
table: tab.table,
columns: tab.columns,
page,
whereClause: tab.filters,
totalRows: tab.totalRows,
itemsPerPage: tab.pagination.itemsPerPage,
},
})
}
function exportTableData(exportData) {
vscode.value.postMessage({
type: 'request:export-table-data',
value: removeProxyWrap(exportData),
})
}
function itemsPerPageChanged(value) {
if (activeTabIndex.value === undefined) return
itemsPerPage.value = value
const tab = displayedTabs.value[activeTabIndex.value]
tab.pagination.itemsPerPage = value
getDataForTabPage(tab, 1)
}
function openSettings(theme) {
vscode.value.postMessage({ type: 'request:open-settings', value: theme })
}
function handleCellChanged(tabId, primaryKeyColumn, primaryKeyValue, columnName, newValue) {
const tabIndex = displayedTabs.value.findIndex(tab => tab.id === tabId)
if (tabIndex !== -1) {
displayedTabs.value[tabIndex].rows = displayedTabs.value[tabIndex].rows.map(row => {
if (row[primaryKeyColumn] === primaryKeyValue) {
row[columnName] = newValue
}
return row
})
}
}
function commitToDatabase(serializedMutations) {
if (!serializedMutations.length) return
const tabId = serializedMutations[0].tabId
editSession.value.push({
tabId,
mutations: serializedMutations.map(mutation => mutation.id),
outcome: 'pending',
})
vscode.value.postMessage({ type: 'request:write-mutations', value: removeProxyWrap(serializedMutations) })
}
function refreshTabById(mutationPayload) {
const tabId = mutationPayload.value.tabId
const tabIndex = displayedTabs.value.findIndex(tab => tab.id === tabId)
if (tabIndex === -1) return
const tab = displayedTabs.value[tabIndex]
vscode.value.postMessage({
type: 'request:get-refreshed-table-data',
value: {
table: tab.table,
itemsPerPage: tab.pagination.itemsPerPage,
},
})
}
function handleMutationSessionCompleted(tabId) {
editSession.value = editSession.value.filter(session => session.tabId !== tabId)
}
function handleRowDeleted(primaryKey, tabId) {
const tabIndex = displayedTabs.value.findIndex(tab => tab.id === tabId)
if (tabIndex === -1) return
displayedTabs.value[tabIndex].rows = displayedTabs.value[tabIndex].rows.filter(row => row.id !== primaryKey)
}
function handleConnectToRemote(connection) {
connectingRemoteId.value = connection.id
vscode.value.postMessage({ type: 'request:connect-to-remote', value: connection.id })
}
function handleSaveRemoteConnection(formData) {
vscode.value.postMessage({ type: 'request:save-remote-connection', value: removeProxyWrap(formData) })
}
function handleDeleteRemoteConnection(connectionId) {
vscode.value.postMessage({ type: 'request:delete-remote-connection', value: connectionId })
}
function handleGetRemoteConnection(connectionId) {
vscode.value.postMessage({ type: 'request:get-remote-connection', value: connectionId })
}
function handleTestRemoteConnection(formData) {
vscode.value.postMessage({ type: 'request:test-remote-connection', value: removeProxyWrap(formData) })
}
function activateLicense() {
vscode.value.postMessage({ type: 'request:activate-license' })
}
function notify(title) {
message.value = title
setTimeout(() => {
message.value = null
}, 1500)
}
</script>
<template>
<!-- eslint-disable vue/no-multiple-template-root -->
<div class="h-full min-h-full w-full min-w-full bg-white">
<!-- eslint-disable vue/valid-v-bind --> <DevDB
ref="devDb"
:activeTabIndex
:connected
:editSession
:providers
:tables
:tabs="displayedTabs"
:userPreferences
:message
:mcpServerConfig
:hasLicense
:remoteConnections
:connectingRemoteId
:loadingTableNames
:loadingTableData
:silentlyRefreshingTab
@cell-value-changed="handleCellChanged"
@commit-mutations="commitToDatabase"
@connect-to-remote="handleConnectToRemote"
@destroy-ui="destroyUi"
@reconnect="reconnect"
@export-table-data="exportTableData"
@get-data-for-tab-page="getDataForTabPage"
@get-fresh-table-data="getFreshTableData"
@items-per-page-changed="itemsPerPageChanged"
@load-table-into-current-tab="loadTableIntoCurrentTab"
@mutation-session-completed="handleMutationSessionCompleted"
@open-settings="openSettings"
@refresh-active-tab="refreshActiveTab"
@refresh-providers="refreshProviders"
@remove-tab="removeTab"
@row-deleted="handleRowDeleted"
@save-remote-connection="handleSaveRemoteConnection"
@delete-remote-connection="handleDeleteRemoteConnection"
@get-remote-connection="handleGetRemoteConnection"
@test-remote-connection="handleTestRemoteConnection"
@select-provider-option="selectProviderOption"
@select-provider="selectProvider"
@switch-to-tab="switchToTab"
@update-current-tab-filter="getFilteredData"
@activate-license="activateLicense"
/>
</div>
<RouterView />
</template>