jsl grid display

This commit is contained in:
Jan Prochazka
2020-04-10 09:50:20 +02:00
parent 949985769c
commit 3bc6f60f75
12 changed files with 152 additions and 61 deletions

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<TableInfo>,
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() {

View File

@@ -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,
}));
}
}

View File

@@ -1,5 +1,6 @@
export * from "./GridDisplay";
export * from "./GridConfig";
export * from "./TableGridDisplay";
export * from "./JslGridDisplay";
export * from "./ChangeSet";
export * from "./filterName";

View File

@@ -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} */

View File

@@ -4,7 +4,9 @@ export interface RangeDefinition {
}
export interface QueryResultColumn {
name: string;
columnName: string;
notNull: boolean;
autoIncrement?: boolean;
}
export interface QueryResult {

View File

@@ -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) {
<TableHead>
<TableHeaderRow ref={headerRowRef}>
<TableHeaderCell data-row="header" data-col="header" />
{visibleRealColumns.map(col => (
{visibleRealColumns.map((col) => (
<TableHeaderCell
data-row="header"
data-col={col.colIndex}
@@ -883,7 +914,7 @@ export default function DataGridCore(props) {
>
<ColumnHeaderControl
column={col}
setSort={order => display.setSort(col.uniqueName, order)}
setSort={(order) => display.setSort(col.uniqueName, order)}
order={display.getSortOrder(col.uniqueName)}
/>
</TableHeaderCell>
@@ -901,7 +932,7 @@ export default function DataGridCore(props) {
</InlineButton>
)}
</TableHeaderCell>
{visibleRealColumns.map(col => (
{visibleRealColumns.map((col) => (
<TableFilterCell
key={col.uniqueName}
style={{ width: col.widthPx, minWidth: col.widthPx, maxWidth: col.widthPx }}
@@ -911,7 +942,7 @@ export default function DataGridCore(props) {
<DataFilterControl
filterType={getFilterType(col.commonType ? col.commonType.typeCode : null)}
filter={display.getFilter(col.uniqueName)}
setFilter={value => display.setFilter(col.uniqueName, value)}
setFilter={(value) => display.setFilter(col.uniqueName, value)}
/>
</TableFilterCell>
))}

View File

@@ -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;
}

View File

@@ -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 <div>{jslid}</div>;
// const display=React.useMemo(()=>)
// return <DataGrid />;
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 <DataGrid display={display} jslid={jslid} />;
}

View File

@@ -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}`;

View File

@@ -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 (
<div>
<MainContainer>
<TabsContainer>
{childrenArray
.filter((x) => x.props)
@@ -51,6 +59,6 @@ export function TabControl({ children }) {
))}
</TabsContainer>
{<TabContainer key={value}>{childrenArray[value] && childrenArray[value].props.children}</TabContainer>}
</div>
</MainContainer>
);
}