diff --git a/packages/api/src/controllers/jsldata.js b/packages/api/src/controllers/jsldata.js index e2a89434b..e233963c2 100644 --- a/packages/api/src/controllers/jsldata.js +++ b/packages/api/src/controllers/jsldata.js @@ -58,13 +58,13 @@ module.exports = { }, getInfo_meta: 'get', - getInfo(jslid) { + getInfo({ jslid }) { const file = path.join(jsldir(), `${jslid}.jsonl.info`); return JSON.parse(fs.readFileSync(file, 'utf-8')); }, getRows_meta: 'get', - async getRows(jslid, offset, limit) { + async getRows({ jslid, offset, limit }) { await this.ensureReader(jslid, offset); const res = []; for (let i = 0; i < limit; i += 1) { diff --git a/packages/api/src/main.js b/packages/api/src/main.js index f4c39d266..42a4e59dc 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -6,12 +6,14 @@ const io = require('socket.io'); const fs = require('fs'); const useController = require('./utility/useController'); +const socket = require('./utility/socket'); + const connections = require('./controllers/connections'); const serverConnections = require('./controllers/serverConnections'); const databaseConnections = require('./controllers/databaseConnections'); const tables = require('./controllers/tables'); const sessions = require('./controllers/sessions'); -const socket = require('./utility/socket'); +const jsldata = require('./controllers/jsldata'); function start() { console.log('process.argv', process.argv); @@ -29,6 +31,7 @@ function start() { useController(app, '/database-connections', databaseConnections); useController(app, '/tables', tables); useController(app, '/sessions', sessions); + useController(app, '/jsldata', jsldata); if (fs.existsSync('/home/dbgate-docker/build')) { // server static files inside docker container diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index 3743da324..aa68860b6 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -7,16 +7,16 @@ import { Select, Expression } from '@dbgate/sqltree'; import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet'; export interface DisplayColumn { - schemaName: string; - pureName: string; + schemaName?: string; + pureName?: string; columnName: string; headerText: string; uniqueName: string; uniquePath: string[]; notNull: boolean; - autoIncrement: boolean; - isPrimaryKey: boolean; - foreignKey: ForeignKeyInfo; + autoIncrement?: boolean; + isPrimaryKey?: boolean; + foreignKey?: ForeignKeyInfo; isChecked?: boolean; hintColumnName?: string; commonType?: DbType; @@ -47,9 +47,11 @@ export abstract class GridDisplay { public cache: GridCache, protected setCache: ChangeCacheFunc, protected getTableInfo: ({ schemaName, pureName }) => Promise, - public driver: EngineDriver + public driver?: EngineDriver ) {} - abstract getPageQuery(offset: number, count: number): string; + getPageQuery(offset: number, count: number): string { + return null; + } columns: DisplayColumn[]; baseTable?: TableInfo; changeSetKeyFields: string[] = null; @@ -65,7 +67,7 @@ export abstract class GridDisplay { } get engine() { - return this.driver.engine; + return this.driver?.engine; } reload() { diff --git a/packages/datalib/src/JslGridDisplay.ts b/packages/datalib/src/JslGridDisplay.ts new file mode 100644 index 000000000..0d847e105 --- /dev/null +++ b/packages/datalib/src/JslGridDisplay.ts @@ -0,0 +1,24 @@ +import { GridDisplay, ChangeCacheFunc } from './GridDisplay'; +import { QueryResultColumn } from '@dbgate/types'; +import { GridConfig, GridCache } from './GridConfig'; + +export class JslGridDisplay extends GridDisplay { + constructor( + jslid, + columns: QueryResultColumn[], + config: GridConfig, + setConfig: (config: GridConfig) => void, + cache: GridCache, + setCache: ChangeCacheFunc + ) { + super(config, setConfig, cache, setCache, null, null); + this.columns = columns.map((col) => ({ + columnName: col.columnName, + headerText: col.columnName, + uniqueName: col.columnName, + uniquePath: [col.columnName], + notNull: col.notNull, + autoIncrement: col.autoIncrement, + })); + } +} diff --git a/packages/datalib/src/index.ts b/packages/datalib/src/index.ts index 2f4c51446..f350abaec 100644 --- a/packages/datalib/src/index.ts +++ b/packages/datalib/src/index.ts @@ -1,5 +1,6 @@ export * from "./GridDisplay"; export * from "./GridConfig"; export * from "./TableGridDisplay"; +export * from "./JslGridDisplay"; export * from "./ChangeSet"; export * from "./filterName"; diff --git a/packages/engines/mssql/index.js b/packages/engines/mssql/index.js index fb67fb873..919cb740d 100644 --- a/packages/engines/mssql/index.js +++ b/packages/engines/mssql/index.js @@ -14,7 +14,12 @@ const dialect = { }; function extractColumns(columns) { - return _.sortBy(_.values(columns), 'index') + return _.sortBy(_.values(columns), 'index').map((col) => ({ + ...col, + columnName: col.name, + notNull: !col.nullable, + autoIncrement: !!col.identity, + })); } /** @type {import('@dbgate/types').EngineDriver} */ diff --git a/packages/types/query.d.ts b/packages/types/query.d.ts index c1da25cea..e3abbccfe 100644 --- a/packages/types/query.d.ts +++ b/packages/types/query.d.ts @@ -4,7 +4,9 @@ export interface RangeDefinition { } export interface QueryResultColumn { - name: string; + columnName: string; + notNull: boolean; + autoIncrement?: boolean; } export interface QueryResult { diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index 43676a89d..7d3aafa65 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -108,6 +108,47 @@ const FocusField = styled.input` top: -1000px; `; +/** @param props {import('./types').DataGridProps} */ +async function loadDataPage(props, offset, limit) { + const { display, conid, database, jslid } = props; + + console.log('LOAD PAGE', jslid); + + if (jslid) { + const response = await axios.request({ + url: 'jsldata/get-rows', + method: 'post', + params: { + jslid, + offset, + limit, + }, + }); + return response.data; + } + + const sql = display.getPageQuery(offset, limit); + + const response = await axios.request({ + url: 'database-connections/query-data', + method: 'post', + params: { + conid, + database, + }, + data: { sql }, + }); + + return response.data.rows; +} + +function dataPageAvailable(props) { + const { display, conid, database, jslid } = props; + if (jslid) return true; + const sql = display.getPageQuery(0, 1); + return !!sql; +} + /** @param props {import('./types').DataGridProps} */ export default function DataGridCore(props) { const { conid, database, display, changeSetState, dispatchChangeSet, tabVisible } = props; @@ -146,8 +187,8 @@ export default function DataGridCore(props) { // const [inplaceEditorShouldSave, setInplaceEditorShouldSave] = React.useState(false); // const [inplaceEditorChangedOnCreate, setInplaceEditorChangedOnCreate] = React.useState(false); - const changeSet = changeSetState.value; - const setChangeSet = React.useCallback(value => dispatchChangeSet({ type: 'set', value }), [dispatchChangeSet]); + const changeSet = changeSetState && changeSetState.value; + const setChangeSet = React.useCallback((value) => dispatchChangeSet({ type: 'set', value }), [dispatchChangeSet]); const changeSetRef = React.useRef(changeSet); @@ -155,8 +196,8 @@ export default function DataGridCore(props) { const autofillMarkerCell = React.useMemo( () => - selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1 - ? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))] + selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map((x) => x[0])).length == 1 + ? [_.max(selectedCells.map((x) => x[0])), _.max(selectedCells.map((x) => x[1]))] : null, [selectedCells] ); @@ -170,17 +211,7 @@ export default function DataGridCore(props) { const loadStart = new Date().getTime(); loadedTimeRef.current = loadStart; - const sql = display.getPageQuery(loadedRows.length, 100); - - const response = await axios.request({ - url: 'database-connections/query-data', - method: 'post', - params: { - conid, - database, - }, - data: { sql }, - }); + const nextRows = await loadDataPage(props, loadedRows.length, 100); if (loadedTimeRef.current !== loadStart) { // new load was dispatched return; @@ -189,7 +220,6 @@ export default function DataGridCore(props) { // console.log('Error loading data from server', nextRows); // nextRows = []; // } - const { rows: nextRows } = response.data; // console.log('nextRows', nextRows); const loadedInfo = { loadedRows: [...loadedRows, ...nextRows], @@ -288,9 +318,10 @@ export default function DataGridCore(props) { firstVisibleRowScrollIndex + visibleRowCountUpperBound >= loadedRows.length && insertedRows.length == 0 ) { - const sql = display.getPageQuery(0, 1); - // try to get SQL, if success, load page. If not, callbacks to load missing metadata are dispatched - if (sql) loadNextData(); + if (dataPageAvailable(props)) { + // If not, callbacks to load missing metadata are dispatched + loadNextData(); + } } if (display.cache.refreshTime > loadedTime) { reload(); @@ -327,7 +358,7 @@ export default function DataGridCore(props) { const realColumnUniqueNames = React.useMemo( () => - _.range(columnSizes.realCount).map(realIndex => (columns[columnSizes.realToModel(realIndex)] || {}).uniqueName), + _.range(columnSizes.realCount).map((realIndex) => (columns[columnSizes.realToModel(realIndex)] || {}).uniqueName), [columnSizes, columns] ); @@ -335,15 +366,15 @@ export default function DataGridCore(props) { const insertedRows = getChangeSetInsertedRows(changeSet, display.baseTable); const rowCountNewIncluded = loadedRows.length + insertedRows.length; - const handleRowScroll = value => { + const handleRowScroll = (value) => { setFirstVisibleRowScrollIndex(value); }; - const handleColumnScroll = value => { + const handleColumnScroll = (value) => { setFirstVisibleColumnScrollIndex(value); }; - const handleContextMenu = event => { + const handleContextMenu = (event) => { event.preventDefault(); showMenu( event.pageX, @@ -407,7 +438,7 @@ export default function DataGridCore(props) { const pasteRows = pastedText .replace(/\r/g, '') .split('\n') - .map(row => row.split('\t')); + .map((row) => row.split('\t')); let chs = changeSet; let allRows = loadedAndInsertedRows; @@ -439,8 +470,8 @@ export default function DataGridCore(props) { } if (selectedCells.length > 1) { const regularSelected = selectedCells.filter(isRegularCell); - const startRow = _.min(regularSelected.map(x => x[0])); - const startCol = _.min(regularSelected.map(x => x[1])); + const startRow = _.min(regularSelected.map((x) => x[0])); + const startCol = _.min(regularSelected.map((x) => x[1])); for (const cell of regularSelected) { const [rowIndex, colIndex] = cell; const selectionRow = rowIndex - startRow; @@ -464,16 +495,16 @@ export default function DataGridCore(props) { } function copyToClipboard() { - const rowIndexes = _.uniq(selectedCells.map(x => x[0])).sort(); - const lines = rowIndexes.map(rowIndex => { + const rowIndexes = _.uniq(selectedCells.map((x) => x[0])).sort(); + const lines = rowIndexes.map((rowIndex) => { const colIndexes = selectedCells - .filter(x => x[0] == rowIndex) - .map(x => x[1]) + .filter((x) => x[0] == rowIndex) + .map((x) => x[1]) .sort(); const rowData = loadedAndInsertedRows[rowIndex]; const line = colIndexes - .map(col => realColumnUniqueNames[col]) - .map(col => (rowData[col] == null ? '' : rowData[col])) + .map((col) => realColumnUniqueNames[col]) + .map((col) => (rowData[col] == null ? '' : rowData[col])) .join('\t'); return line; }); @@ -485,7 +516,7 @@ export default function DataGridCore(props) { if (autofillDragStartCell) { const cell = cellFromEvent(event); if (isRegularCell(cell) && (cell[0] == autofillDragStartCell[0] || cell[1] == autofillDragStartCell[1])) { - const autoFillStart = [selectedCells[0][0], _.min(selectedCells.map(x => x[1]))]; + const autoFillStart = [selectedCells[0][0], _.min(selectedCells.map((x) => x[1]))]; // @ts-ignore setAutofillSelectedCells(getCellRange(autoFillStart, cell)); } @@ -506,9 +537,9 @@ export default function DataGridCore(props) { if (autofillDragStartCell) { const currentRowNumber = currentCell[0]; if (_.isNumber(currentRowNumber)) { - const rowIndexes = _.uniq((autofillSelectedCells || []).map(x => x[0])).filter(x => x != currentRowNumber); + const rowIndexes = _.uniq((autofillSelectedCells || []).map((x) => x[0])).filter((x) => x != currentRowNumber); // @ts-ignore - const colNames = selectedCells.map(cell => realColumnUniqueNames[cell[1]]); + const colNames = selectedCells.map((cell) => realColumnUniqueNames[cell[1]]); const changeObject = _.pick(loadedAndInsertedRows[currentRowNumber], colNames); setChangeSet( batchUpdateChangeSet( @@ -539,7 +570,7 @@ export default function DataGridCore(props) { } function getSelectedRowDefinitions() { - return getRowDefinitions(_.uniq((selectedCells || []).map(x => x[0]))); + return getRowDefinitions(_.uniq((selectedCells || []).map((x) => x[0]))); } function revertRowChanges() { @@ -874,7 +905,7 @@ export default function DataGridCore(props) { - {visibleRealColumns.map(col => ( + {visibleRealColumns.map((col) => ( display.setSort(col.uniqueName, order)} + setSort={(order) => display.setSort(col.uniqueName, order)} order={display.getSortOrder(col.uniqueName)} /> @@ -901,7 +932,7 @@ export default function DataGridCore(props) { )} - {visibleRealColumns.map(col => ( + {visibleRealColumns.map((col) => ( display.setFilter(col.uniqueName, value)} + setFilter={(value) => display.setFilter(col.uniqueName, value)} /> ))} diff --git a/packages/web/src/datagrid/types.ts b/packages/web/src/datagrid/types.ts index fd4c8e804..fb9aaeec7 100644 --- a/packages/web/src/datagrid/types.ts +++ b/packages/web/src/datagrid/types.ts @@ -1,11 +1,12 @@ import { GridDisplay, ChangeSet } from '@dbgate/datalib'; export interface DataGridProps { - conid?: number; + conid?: string; database?: string; display: GridDisplay; tabVisible?: boolean; changeSetState?: { value: ChangeSet }; dispatchChangeSet?: Function; toolbarPortalRef?: any; + jslid?: string; } diff --git a/packages/web/src/sqleditor/JslDataGrid.js b/packages/web/src/sqleditor/JslDataGrid.js index 0d97e23e2..c7828abb3 100644 --- a/packages/web/src/sqleditor/JslDataGrid.js +++ b/packages/web/src/sqleditor/JslDataGrid.js @@ -1,8 +1,20 @@ import React from 'react'; import DataGrid from '../datagrid/DataGrid'; +import { JslGridDisplay, createGridConfig, createGridCache } from '@dbgate/datalib'; +import useFetch from '../utility/useFetch'; export default function JslDataGrid({ jslid }) { - return
{jslid}
; - // const display=React.useMemo(()=>) - // return ; + const columns = useFetch({ + params: { jslid }, + url: 'jsldata/get-info', + defaultValue: [], + }); + const [config, setConfig] = React.useState(createGridConfig()); + const [cache, setCache] = React.useState(createGridCache()); + const display = React.useMemo(() => new JslGridDisplay(jslid, columns, config, setConfig, cache, setCache), [ + jslid, + columns, + ]); + + return ; } diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index 396f39934..6428d89fc 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -21,7 +21,9 @@ const EditorContainer = styled.div` position: relative; `; -const MessagesContainer = styled.div``; +const MessagesContainer = styled.div` + height: 200px; +`; export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPortalRef }) { const localStorageKey = `sql_${tabid}`; diff --git a/packages/web/src/widgets/TabControl.js b/packages/web/src/widgets/TabControl.js index 8f75bfb8f..18aeba1f8 100644 --- a/packages/web/src/widgets/TabControl.js +++ b/packages/web/src/widgets/TabControl.js @@ -22,7 +22,10 @@ const TabNameWrapper = styled.span` margin-left: 5px; `; -const TabContainer = styled.div``; +const TabContainer = styled.div` + position: relative; + flex-grow: 1; +`; const TabsContainer = styled.div` display: flex; @@ -31,6 +34,11 @@ const TabsContainer = styled.div` background-color: ${theme.tabsPanel.background}; `; +const MainContainer = styled.div` + display: flex; + flex-direction: column; +`; + export function TabPage({ label = undefined, children }) { return children; } @@ -39,7 +47,7 @@ export function TabControl({ children }) { const [value, setValue] = React.useState(0); const childrenArray = (_.isArray(children) ? _.flatten(children) : [children]).filter((x) => x); return ( -
+ {childrenArray .filter((x) => x.props) @@ -51,6 +59,6 @@ export function TabControl({ children }) { ))} {{childrenArray[value] && childrenArray[value].props.children}} -
+ ); }