Merge branch 'formview'

This commit is contained in:
Jan Prochazka 2021-01-11 18:18:34 +01:00
commit b44f49a492
22 changed files with 1367 additions and 38 deletions

View File

@ -28,6 +28,7 @@ module.exports = {
handle_status(conid, database, { status }) {
const existing = this.opened.find((x) => x.conid == conid && x.database == database);
if (!existing) return;
if (existing.status == status) return;
existing.status = status;
socket.emitChanged(`database-status-changed-${conid}-${database}`);
},

View File

@ -38,6 +38,7 @@ async function handleIncrementalRefresh() {
analysedStructure = newStructure;
process.send({ msgtype: 'structure', structure: analysedStructure });
}
setStatusName('ok');
}
function setStatus(status) {

View File

@ -0,0 +1,25 @@
import _ from 'lodash';
import { GridConfig, GridCache, GridConfigColumns, createGridCache, GroupFunc } from './GridConfig';
import { ForeignKeyInfo, TableInfo, ColumnInfo, EngineDriver, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import { parseFilter, getFilterType } from 'dbgate-filterparser';
import { filterName } from './filterName';
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
import { Expression, Select, treeToSql, dumpSqlSelect, Condition } from 'dbgate-sqltree';
import { isTypeLogical } from 'dbgate-tools';
import { ChangeCacheFunc, ChangeConfigFunc, DisplayColumn } from './GridDisplay';
export class FormViewDisplay {
isLoadedCorrectly = true;
columns: DisplayColumn[];
public baseTable: TableInfo;
constructor(
public config: GridConfig,
protected setConfig: ChangeConfigFunc,
public cache: GridCache,
protected setCache: ChangeCacheFunc,
public driver?: EngineDriver,
public dbinfo: DatabaseInfo = null
) {}
}

View File

@ -29,6 +29,8 @@ export interface GridConfig extends GridConfigColumns {
grouping: { [uniqueName: string]: GroupFunc };
childConfig?: GridConfig;
reference?: GridReferenceDefinition;
isFormView?: boolean;
formViewKey?: { [uniqueName: string]: string };
}
export interface GridCache {

View File

@ -518,4 +518,20 @@ export abstract class GridDisplay {
conditions,
};
}
switchToFormView(rowData) {
if (!this.baseTable) return;
const { primaryKey } = this.baseTable;
if (!primaryKey) return;
const { columns } = primaryKey;
this.setConfig((cfg) => ({
...cfg,
isFormView: true,
formViewKey: _.pick(
rowData,
columns.map((x) => x.columnName)
),
}));
}
}

View File

@ -0,0 +1,251 @@
import { FormViewDisplay } from './FormViewDisplay';
import _ from 'lodash';
import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay';
import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import { GridConfig, GridCache, createGridCache } from './GridConfig';
import {
Expression,
Select,
treeToSql,
dumpSqlSelect,
mergeConditions,
Condition,
OrderByExpression,
} from 'dbgate-sqltree';
import { filterName } from './filterName';
import { TableGridDisplay } from './TableGridDisplay';
import stableStringify from 'json-stable-stringify';
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
export class TableFormViewDisplay extends FormViewDisplay {
// use utility functions from GridDisplay and publish result in FromViewDisplat interface
private gridDisplay: TableGridDisplay;
constructor(
public tableName: NamedObjectInfo,
driver: EngineDriver,
config: GridConfig,
setConfig: ChangeConfigFunc,
cache: GridCache,
setCache: ChangeCacheFunc,
dbinfo: DatabaseInfo
) {
super(config, setConfig, cache, setCache, driver, dbinfo);
this.gridDisplay = new TableGridDisplay(tableName, driver, config, setConfig, cache, setCache, dbinfo);
this.isLoadedCorrectly = this.gridDisplay.isLoadedCorrectly;
this.columns = this.gridDisplay.columns;
this.baseTable = this.gridDisplay.baseTable;
}
getPrimaryKeyEqualCondition(row = null): Condition {
if (!row) row = this.config.formViewKey;
if (!row) return null;
const { primaryKey } = this.gridDisplay.baseTable;
if (!primaryKey) return null;
return {
conditionType: 'and',
conditions: primaryKey.columns.map(({ columnName }) => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName,
source: {
alias: 'basetbl',
},
},
right: {
exprType: 'value',
value: this.config.formViewKey[columnName],
},
})),
};
}
getPrimaryKeyOperatorCondition(operator): Condition {
if (!this.config.formViewKey) return null;
const conditions = [];
const { primaryKey } = this.gridDisplay.baseTable;
if (!primaryKey) return null;
for (let index = 0; index < primaryKey.columns.length; index++) {
conditions.push({
conditionType: 'and',
conditions: [
...primaryKey.columns.slice(0, index).map(({ columnName }) => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName,
source: {
alias: 'basetbl',
},
},
right: {
exprType: 'value',
value: this.config.formViewKey[columnName],
},
})),
...primaryKey.columns.slice(index).map(({ columnName }) => ({
conditionType: 'binary',
operator: operator,
left: {
exprType: 'column',
columnName,
source: {
alias: 'basetbl',
},
},
right: {
exprType: 'value',
value: this.config.formViewKey[columnName],
},
})),
],
});
}
if (conditions.length == 1) {
return conditions[0];
}
return {
conditionType: 'or',
conditions,
};
}
getSelect() {
if (!this.driver) return null;
const select = this.gridDisplay.createSelect();
if (!select) return null;
select.topRecords = 1;
return select;
}
getCurrentRowQuery() {
const select = this.getSelect();
if (!select) return null;
select.where = mergeConditions(select.where, this.getPrimaryKeyEqualCondition());
const sql = treeToSql(this.driver, select, dumpSqlSelect);
return sql;
}
getCountSelect() {
const select = this.getSelect();
if (!select) return null;
select.orderBy = null;
select.columns = [
{
exprType: 'raw',
sql: 'COUNT(*)',
alias: 'count',
},
];
select.topRecords = null;
return select;
}
getCountQuery() {
if (!this.driver) return null;
const select = this.getCountSelect();
if (!select) return null;
const sql = treeToSql(this.driver, select, dumpSqlSelect);
return sql;
}
getBeforeCountQuery() {
if (!this.driver) return null;
const select = this.getCountSelect();
if (!select) return null;
select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('<'));
const sql = treeToSql(this.driver, select, dumpSqlSelect);
return sql;
}
extractKey(row) {
if (!row || !this.gridDisplay.baseTable || !this.gridDisplay.baseTable.primaryKey) {
return null;
}
const formViewKey = _.pick(
row,
this.gridDisplay.baseTable.primaryKey.columns.map((x) => x.columnName)
);
return formViewKey;
}
navigate(row) {
const formViewKey = this.extractKey(row);
this.setConfig((cfg) => ({
...cfg,
formViewKey,
}));
}
isLoadedCurrentRow(row) {
console.log('isLoadedCurrentRow', row, this.config.formViewKey);
if (!row) return false;
const formViewKey = this.extractKey(row);
return stableStringify(formViewKey) == stableStringify(this.config.formViewKey);
}
navigateRowQuery(commmand: 'begin' | 'previous' | 'next' | 'end') {
if (!this.driver) return null;
const select = this.gridDisplay.createSelect();
if (!select) return null;
const { primaryKey } = this.gridDisplay.baseTable;
function getOrderBy(direction): OrderByExpression[] {
return primaryKey.columns.map(({ columnName }) => ({
exprType: 'column',
columnName,
direction,
}));
}
select.topRecords = 1;
switch (commmand) {
case 'begin':
select.orderBy = getOrderBy('ASC');
break;
case 'end':
select.orderBy = getOrderBy('DESC');
break;
case 'previous':
select.orderBy = getOrderBy('DESC');
select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('<'));
break;
case 'next':
select.orderBy = getOrderBy('ASC');
select.where = mergeConditions(select.where, this.getPrimaryKeyOperatorCondition('>'));
break;
}
const sql = treeToSql(this.driver, select, dumpSqlSelect);
return sql;
}
getChangeSetRow(row): ChangeSetRowDefinition {
if (!this.baseTable) return null;
return {
pureName: this.baseTable.pureName,
schemaName: this.baseTable.schemaName,
condition: this.extractKey(row),
};
}
getChangeSetField(row, uniqueName): ChangeSetFieldDefinition {
const col = this.columns.find((x) => x.uniqueName == uniqueName);
if (!col) return null;
if (!this.baseTable) return null;
if (this.baseTable.pureName != col.pureName || this.baseTable.schemaName != col.schemaName) return null;
return {
...this.getChangeSetRow(row),
uniqueName: uniqueName,
columnName: col.columnName,
};
}
}

View File

@ -1,11 +1,13 @@
export * from "./GridDisplay";
export * from "./GridConfig";
export * from "./TableGridDisplay";
export * from "./ViewGridDisplay";
export * from "./JslGridDisplay";
export * from "./ChangeSet";
export * from "./filterName";
export * from "./FreeTableGridDisplay";
export * from "./FreeTableModel";
export * from "./MacroDefinition";
export * from "./runMacro";
export * from './GridDisplay';
export * from './GridConfig';
export * from './TableGridDisplay';
export * from './ViewGridDisplay';
export * from './JslGridDisplay';
export * from './ChangeSet';
export * from './filterName';
export * from './FreeTableGridDisplay';
export * from './FreeTableModel';
export * from './MacroDefinition';
export * from './runMacro';
export * from './FormViewDisplay';
export * from './TableFormViewDisplay';

View File

@ -52,15 +52,18 @@ function autodetect(selection, grider, value) {
return 'textWrap';
}
export default function CellDataView({ selection, grider }) {
export default function CellDataView({ selection = undefined, grider = undefined, selectedValue = undefined }) {
const [selectedFormatType, setSelectedFormatType] = React.useState('autodetect');
const theme = useTheme();
let value = null;
if (grider && selection.length == 1) {
if (grider && selection && selection.length == 1) {
const rowData = grider.getRowData(selection[0].row);
const { column } = selection[0];
if (rowData) value = rowData[column];
}
if (selectedValue) {
value = selectedValue;
}
const autodetectFormatType = React.useMemo(() => autodetect(selection, grider, value), [selection, grider, value]);
const autodetectFormat = formats.find((x) => x.type == autodetectFormatType);

View File

@ -21,31 +21,50 @@ const DataGridContainer = styled.div`
`;
export default function DataGrid(props) {
const { GridCore } = props;
const { GridCore, FormView, config, formDisplay } = props;
const theme = useTheme();
const [managerSize, setManagerSize] = React.useState(0);
const [selection, setSelection] = React.useState([]);
const [formSelection, setFormSelection] = React.useState(null);
const [grider, setGrider] = React.useState(null);
// const [formViewData, setFormViewData] = React.useState(null);
const isFormView = !!(config && config.isFormView);
return (
<HorizontalSplitter initialValue="300px" size={managerSize} setSize={setManagerSize}>
<LeftContainer theme={theme}>
<WidgetColumnBar>
<WidgetColumnBarItem title="Columns" name="columns" height={props.showReferences ? '40%' : '60%'}>
<ColumnManager {...props} managerSize={managerSize} />
</WidgetColumnBarItem>
{!isFormView && (
<WidgetColumnBarItem title="Columns" name="columns" height={props.showReferences ? '40%' : '60%'}>
<ColumnManager {...props} managerSize={managerSize} />
</WidgetColumnBarItem>
)}
{props.showReferences && props.display.hasReferences && (
<WidgetColumnBarItem title="References" name="references" height="30%" collapsed={props.isDetailView}>
<ReferenceManager {...props} managerSize={managerSize} />
</WidgetColumnBarItem>
)}
<WidgetColumnBarItem title="Cell data" name="cellData" collapsed={props.isDetailView}>
<CellDataView selection={selection} grider={grider} />
{isFormView ? (
<CellDataView selectedValue={formSelection} />
) : (
<CellDataView selection={selection} grider={grider} />
)}
</WidgetColumnBarItem>
</WidgetColumnBar>
</LeftContainer>
<DataGridContainer>
<GridCore {...props} onSelectionChanged={setSelection} onChangeGrider={setGrider} />
{isFormView ? (
<FormView {...props} onSelectionChanged={setFormSelection} />
) : (
<GridCore
{...props}
onSelectionChanged={setSelection}
onChangeGrider={setGrider}
formViewAvailable={!!FormView && !!formDisplay}
/>
)}
</DataGridContainer>
</HorizontalSplitter>
);

View File

@ -14,6 +14,7 @@ export default function DataGridContextMenu({
openFreeTable,
openChartSelection,
openActiveChart,
switchToForm,
}) {
return (
<>
@ -57,6 +58,11 @@ export default function DataGridContextMenu({
<DropDownMenuItem onClick={openFreeTable}>Open selection in free table editor</DropDownMenuItem>
<DropDownMenuItem onClick={openChartSelection}>Open chart from selection</DropDownMenuItem>
{openActiveChart && <DropDownMenuItem onClick={openActiveChart}>Open active chart</DropDownMenuItem>}
{!!switchToForm && (
<DropDownMenuItem onClick={switchToForm} keyText="F4">
Form view
</DropDownMenuItem>
)}
</>
);
}

View File

@ -116,6 +116,7 @@ export default function DataGridCore(props) {
onSelectionChanged,
frameSelection,
onKeyDown,
formViewAvailable,
} = props;
// console.log('RENDER GRID', display.baseTable.pureName);
const columns = React.useMemo(() => display.allColumns, [display]);
@ -381,6 +382,7 @@ export default function DataGridCore(props) {
openFreeTable={handleOpenFreeTable}
openChartSelection={handleOpenChart}
openActiveChart={openActiveChart}
switchToForm={handleSwitchToFormView}
/>
);
};
@ -719,6 +721,11 @@ export default function DataGridCore(props) {
display.reload();
}
if (event.keyCode == keycodes.f4) {
event.preventDefault();
handleSwitchToFormView();
}
if (event.keyCode == keycodes.s && event.ctrlKey) {
event.preventDefault();
handleSave();
@ -942,6 +949,24 @@ export default function DataGridCore(props) {
display.clearFilters();
};
const handleSetFormView =
formViewAvailable && display.baseTable && display.baseTable.primaryKey
? (rowData) => {
display.switchToFormView(rowData);
}
: null;
const handleSwitchToFormView =
formViewAvailable && display.baseTable && display.baseTable.primaryKey
? () => {
const cell = currentCell;
if (!isRegularCell(cell)) return;
const rowData = grider.getRowData(cell[0]);
if (!rowData) return;
display.switchToFormView(rowData);
}
: null;
// console.log('visibleRealColumnIndexes', visibleRealColumnIndexes);
// console.log(
// 'gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()',
@ -1047,6 +1072,7 @@ export default function DataGridCore(props) {
display={display}
focusedColumn={display.focusedColumn}
frameSelection={frameSelection}
onSetFormView={handleSetFormView}
/>
)
)}
@ -1081,6 +1107,7 @@ export default function DataGridCore(props) {
await axios.post('database-connections/refresh', { conid, database });
display.reload();
}}
switchToForm={handleSwitchToFormView}
/>,
props.toolbarPortalRef.current
)}

View File

@ -7,6 +7,7 @@ import InplaceEditor from './InplaceEditor';
import { cellIsSelected } from './gridutil';
import { isTypeLogical } from 'dbgate-tools';
import useTheme from '../theme/useTheme';
import { FontIcon } from '../icons';
const TableBodyCell = styled.td`
font-weight: normal;
@ -114,6 +115,7 @@ const TableHeaderCell = styled.td`
padding: 2px;
background-color: ${(props) => props.theme.gridheader_background};
overflow: hidden;
position: relative;
`;
const AutoFillPoint = styled.div`
@ -127,6 +129,16 @@ const AutoFillPoint = styled.div`
cursor: crosshair;
`;
const ShowFormButton = styled.div`
position: absolute;
right: 2px;
top: 2px;
&:hover {
background-color: ${(props) => props.theme.gridheader_background_blue[4]};
border: 1px solid ${(props) => props.theme.border};
}
`;
function makeBulletString(value) {
return _.pad('', value.length, '•');
}
@ -142,7 +154,7 @@ function highlightSpecialCharacters(value) {
const dateTimeRegex = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?$/;
function CellFormattedValue({ value, dataType }) {
export function CellFormattedValue({ value, dataType }) {
if (value == null) return <NullSpan>(NULL)</NullSpan>;
if (_.isDate(value)) return moment(value).format('YYYY-MM-DD HH:mm:ss');
if (value === true) return '1';
@ -167,6 +179,33 @@ function CellFormattedValue({ value, dataType }) {
return value.toString();
}
function RowHeaderCell({ rowIndex, theme, onSetFormView, rowData }) {
const [mouseIn, setMouseIn] = React.useState(false);
return (
<TableHeaderCell
data-row={rowIndex}
data-col="header"
theme={theme}
onMouseEnter={onSetFormView ? () => setMouseIn(true) : null}
onMouseLeave={onSetFormView ? () => setMouseIn(false) : null}
>
{rowIndex + 1}
{!!onSetFormView && mouseIn && (
<ShowFormButton
theme={theme}
onClick={(e) => {
e.stopPropagation();
onSetFormView(rowData);
}}
>
<FontIcon icon="icon form" />
</ShowFormButton>
)}
</TableHeaderCell>
);
}
/** @param props {import('./types').DataGridProps} */
function DataGridRow(props) {
const {
@ -181,6 +220,7 @@ function DataGridRow(props) {
focusedColumn,
grider,
frameSelection,
onSetFormView,
} = props;
// usePropsCompare({
// rowHeight,
@ -217,9 +257,8 @@ function DataGridRow(props) {
return (
<TableBodyRow style={{ height: `${rowHeight}px` }} theme={theme}>
<TableHeaderCell data-row={rowIndex} data-col="header" theme={theme}>
{rowIndex + 1}
</TableHeaderCell>
<RowHeaderCell rowIndex={rowIndex} theme={theme} onSetFormView={onSetFormView} rowData={rowData} />
{visibleRealColumns.map((col) => (
<TableBodyCell
key={col.uniqueName}
@ -252,9 +291,10 @@ function DataGridRow(props) {
inplaceEditorState={inplaceEditorState}
dispatchInsplaceEditor={dispatchInsplaceEditor}
cellValue={rowData[col.uniqueName]}
grider={grider}
rowIndex={rowIndex}
uniqueName={col.uniqueName}
// grider={grider}
// rowIndex={rowIndex}
// uniqueName={col.uniqueName}
onSetValue={(value) => grider.setCellValue(rowIndex, col.uniqueName, value)}
/>
) : (
<>

View File

@ -1,9 +1,14 @@
import React from 'react';
import ToolbarButton from '../widgets/ToolbarButton';
export default function DataGridToolbar({ reload, reconnect, grider, save }) {
export default function DataGridToolbar({ reload, reconnect, grider, save, switchToForm }) {
return (
<>
{switchToForm && (
<ToolbarButton onClick={switchToForm} icon="icon form">
Form view
</ToolbarButton>
)}
<ToolbarButton onClick={reload} icon="icon reload">
Refresh
</ToolbarButton>

View File

@ -14,14 +14,16 @@ const StyledInput = styled.input`
export default function InplaceEditor({
widthPx,
rowIndex,
uniqueName,
grider,
// rowIndex,
// uniqueName,
// grider,
cellValue,
inplaceEditorState,
dispatchInsplaceEditor,
onSetValue,
}) {
const editorRef = React.useRef();
const widthRef = React.useRef(widthPx);
const isChangedRef = React.useRef(!!inplaceEditorState.text);
React.useEffect(() => {
const editor = editorRef.current;
@ -34,7 +36,8 @@ export default function InplaceEditor({
function handleBlur() {
if (isChangedRef.current) {
const editor = editorRef.current;
grider.setCellValue(rowIndex, uniqueName, editor.value);
onSetValue(editor.value);
// grider.setCellValue(rowIndex, uniqueName, editor.value);
isChangedRef.current = false;
}
dispatchInsplaceEditor({ type: 'close' });
@ -42,7 +45,8 @@ export default function InplaceEditor({
if (inplaceEditorState.shouldSave) {
const editor = editorRef.current;
if (isChangedRef.current) {
grider.setCellValue(rowIndex, uniqueName, editor.value);
onSetValue(editor.value);
// grider.setCellValue(rowIndex, uniqueName, editor.value);
isChangedRef.current = false;
}
editor.blur();
@ -57,7 +61,8 @@ export default function InplaceEditor({
break;
case keycodes.enter:
if (isChangedRef.current) {
grider.setCellValue(rowIndex, uniqueName, editor.value);
// grider.setCellValue(rowIndex, uniqueName, editor.value);
onSetValue(editor.value);
isChangedRef.current = false;
}
editor.blur();
@ -66,7 +71,8 @@ export default function InplaceEditor({
case keycodes.s:
if (event.ctrlKey) {
if (isChangedRef.current) {
grider.setCellValue(rowIndex, uniqueName, editor.value);
onSetValue(editor.value);
// grider.setCellValue(rowIndex, uniqueName, editor.value);
isChangedRef.current = false;
}
event.preventDefault();
@ -83,9 +89,9 @@ export default function InplaceEditor({
onChange={() => (isChangedRef.current = true)}
onKeyDown={handleKeyDown}
style={{
width: widthPx,
minWidth: widthPx,
maxWidth: widthPx,
width: widthRef.current,
minWidth: widthRef.current,
maxWidth: widthRef.current,
}}
/>
);

View File

@ -2,7 +2,7 @@ import React from 'react';
import _ from 'lodash';
import DataGrid from './DataGrid';
import styled from 'styled-components';
import { TableGridDisplay, createGridConfig, createGridCache } from 'dbgate-datalib';
import { TableGridDisplay, TableFormViewDisplay, createGridConfig, createGridCache } from 'dbgate-datalib';
import { getFilterValueExpression } from 'dbgate-filterparser';
import { findEngineDriver } from 'dbgate-tools';
import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders';
@ -12,6 +12,7 @@ import stableStringify from 'json-stable-stringify';
import ReferenceHeader from './ReferenceHeader';
import SqlDataGridCore from './SqlDataGridCore';
import useExtensions from '../utility/useExtensions';
import SqlFormView from '../formview/SqlFormView';
const ReferenceContainer = styled.div`
position: absolute;
@ -87,7 +88,22 @@ export default function TableDataGrid({
: null;
}
function createFormDisplay() {
return connection
? new TableFormViewDisplay(
{ schemaName, pureName },
findEngineDriver(connection, extensions),
config,
setConfig,
cache || myCache,
setCache || setMyCache,
dbinfo
)
: null;
}
const [display, setDisplay] = React.useState(createDisplay());
const [formDisplay, setFormDisplay] = React.useState(createFormDisplay());
React.useEffect(() => {
setRefReloadToken((v) => v + 1);
@ -101,6 +117,13 @@ export default function TableDataGrid({
setDisplay(newDisplay);
}, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]);
React.useEffect(() => {
const newDisplay = createFormDisplay();
if (!newDisplay) return;
if (formDisplay && formDisplay.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return;
setFormDisplay(newDisplay);
}, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]);
const handleDatabaseStructureChanged = React.useCallback(() => {
(setCache || setMyCache)(createGridCache());
}, []);
@ -158,9 +181,12 @@ export default function TableDataGrid({
<VerticalSplitter>
<DataGrid
// key={`${conid}, ${database}, ${schemaName}, ${pureName}`}
config={config}
setConfig={setConfig}
conid={conid}
database={database}
display={display}
formDisplay={formDisplay}
tabVisible={tabVisible}
changeSetState={changeSetState}
dispatchChangeSet={dispatchChangeSet}
@ -171,7 +197,11 @@ export default function TableDataGrid({
refReloadToken={refReloadToken.toString()}
masterLoadedTime={masterLoadedTime}
GridCore={SqlDataGridCore}
FormView={SqlFormView}
isDetailView={isDetailView}
// tableInfo={
// dbinfo && dbinfo.tables && dbinfo.tables.find((x) => x.pureName == pureName && x.schemaName == schemaName)
// }
/>
{reference && (
<ReferenceContainer>

View File

@ -0,0 +1,93 @@
import {
ChangeSet,
changeSetContainsChanges,
changeSetInsertNewRow,
createChangeSet,
deleteChangeSetRows,
findExistingChangeSetItem,
getChangeSetInsertedRows,
TableFormViewDisplay,
revertChangeSetRowChanges,
setChangeSetValue,
ChangeSetRowDefinition,
} from 'dbgate-datalib';
import Former from './Former';
export default class ChangeSetFormer extends Former {
public changeSet: ChangeSet;
public setChangeSet: Function;
private batchChangeSet: ChangeSet;
public rowDefinition: ChangeSetRowDefinition;
public rowStatus;
constructor(
public sourceRow: any,
public changeSetState,
public dispatchChangeSet,
public display: TableFormViewDisplay
) {
super();
this.changeSet = changeSetState && changeSetState.value;
this.setChangeSet = (value) => dispatchChangeSet({ type: 'set', value });
this.batchChangeSet = null;
this.rowDefinition = display.getChangeSetRow(sourceRow);
const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, this.rowDefinition);
this.rowData = matchedChangeSetItem ? { ...sourceRow, ...matchedChangeSetItem.fields } : sourceRow;
let status = 'regular';
if (matchedChangeSetItem && matchedField == 'updates') status = 'updated';
if (matchedField == 'deletes') status = 'deleted';
this.rowStatus = {
status,
modifiedFields:
matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null,
};
}
applyModification(changeSetReducer) {
if (this.batchChangeSet) {
this.batchChangeSet = changeSetReducer(this.batchChangeSet);
} else {
this.setChangeSet(changeSetReducer(this.changeSet));
}
}
setCellValue( uniqueName: string, value: any) {
const row = this.sourceRow;
const definition = this.display.getChangeSetField(row, uniqueName);
this.applyModification((chs) => setChangeSetValue(chs, definition, value));
}
deleteRow(index: number) {
this.applyModification((chs) => deleteChangeSetRows(chs, this.rowDefinition));
}
beginUpdate() {
this.batchChangeSet = this.changeSet;
}
endUpdate() {
this.setChangeSet(this.batchChangeSet);
this.batchChangeSet = null;
}
revertRowChanges() {
this.applyModification((chs) => revertChangeSetRowChanges(chs, this.rowDefinition));
}
revertAllChanges() {
this.applyModification((chs) => createChangeSet());
}
undo() {
this.dispatchChangeSet({ type: 'undo' });
}
redo() {
this.dispatchChangeSet({ type: 'redo' });
}
get canUndo() {
return this.changeSetState.canUndo;
}
get canRedo() {
return this.changeSetState.canRedo;
}
get containsChanges() {
return changeSetContainsChanges(this.changeSet);
}
}

View File

@ -0,0 +1,499 @@
// @ts-nocheck
import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import ColumnLabel from '../datagrid/ColumnLabel';
import { findForeignKeyForColumn } from 'dbgate-tools';
import styled from 'styled-components';
import useTheme from '../theme/useTheme';
import useDimensions from '../utility/useDimensions';
import FormViewToolbar from './FormViewToolbar';
import { useShowMenu } from '../modals/showMenu';
import FormViewContextMenu from './FormViewContextMenu';
import keycodes from '../utility/keycodes';
import { CellFormattedValue } from '../datagrid/DataGridRow';
import { cellFromEvent } from '../datagrid/selection';
import InplaceEditor from '../datagrid/InplaceEditor';
import { copyTextToClipboard } from '../utility/clipboard';
const Table = styled.table`
border-collapse: collapse;
outline: none;
`;
const Wrapper = styled.div`
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
display: flex;
overflow-x: scroll;
`;
const TableRow = styled.tr`
background-color: ${(props) => props.theme.gridbody_background};
&:nth-child(6n + 3) {
background-color: ${(props) => props.theme.gridbody_background_alt2};
}
&:nth-child(6n + 6) {
background-color: ${(props) => props.theme.gridbody_background_alt3};
}
`;
const TableHeaderCell = styled.td`
border: 1px solid ${(props) => props.theme.border};
text-align: left;
padding: 2px;
background-color: ${(props) => props.theme.gridheader_background};
overflow: hidden;
position: relative;
${(props) =>
props.isSelected &&
`
background: initial;
background-color: ${props.theme.gridbody_selection[4]};
color: ${props.theme.gridbody_invfont1};`}
`;
const TableBodyCell = styled.td`
font-weight: normal;
border: 1px solid ${(props) => props.theme.border};
// border-collapse: collapse;
padding: 2px;
white-space: nowrap;
position: relative;
max-width: 500px;
overflow: hidden;
text-overflow: ellipsis;
${(props) =>
props.isSelected &&
`
background: initial;
background-color: ${props.theme.gridbody_selection[4]};
color: ${props.theme.gridbody_invfont1};`}
${(props) =>
!props.isSelected &&
props.isModifiedCell &&
`
background-color: ${props.theme.gridbody_background_orange[1]};`}
`;
const FocusField = styled.input`
// visibility: hidden
position: absolute;
left: -1000px;
top: -1000px;
`;
const RowCountLabel = styled.div`
position: absolute;
background-color: ${(props) => props.theme.gridbody_background_yellow[1]};
right: 40px;
bottom: 20px;
`;
const HintSpan = styled.span`
color: gray;
margin-left: 5px;
`;
function isDataCell(cell) {
return cell[1] % 2 == 1;
}
export default function FormView(props) {
const {
toolbarPortalRef,
tabVisible,
config,
setConfig,
onNavigate,
former,
onSave,
conid,
database,
onReload,
onReconnect,
allRowCount,
rowCountBefore,
onSelectionChanged,
} = props;
/** @type {import('dbgate-datalib').FormViewDisplay} */
const formDisplay = props.formDisplay;
const theme = useTheme();
const [headerRowRef, { height: rowHeight }] = useDimensions();
const [wrapperRef, { height: wrapperHeight }] = useDimensions();
const showMenu = useShowMenu();
const focusFieldRef = React.useRef(null);
const [currentCell, setCurrentCell] = React.useState([0, 0]);
const cellRefs = React.useRef({});
const rowCount = Math.floor((wrapperHeight - 20) / rowHeight);
const columnChunks = _.chunk(formDisplay.columns, rowCount);
const { rowData, rowStatus } = former;
const handleSwitchToTable = () => {
setConfig((cfg) => ({
...cfg,
isFormView: false,
formViewKey: null,
}));
};
const handleContextMenu = (event) => {
event.preventDefault();
showMenu(
event.pageX,
event.pageY,
<FormViewContextMenu switchToTable={handleSwitchToTable} onNavigate={onNavigate} />
);
};
const setCellRef = (row, col, element) => {
cellRefs.current[`${row},${col}`] = element;
};
React.useEffect(() => {
if (tabVisible) {
if (focusFieldRef.current) focusFieldRef.current.focus();
}
}, [tabVisible, focusFieldRef.current]);
React.useEffect(() => {
if (!onSelectionChanged || !rowData) return;
const col = getCellColumn(currentCell);
if (!col) return;
onSelectionChanged(rowData[col.uniqueName]);
}, [onSelectionChanged, currentCell, rowData]);
const checkMoveCursorBounds = (row, col) => {
if (row < 0) row = 0;
if (col < 0) col = 0;
if (col >= columnChunks.length * 2) col = columnChunks.length * 2 - 1;
const chunk = columnChunks[Math.floor(col / 2)];
if (chunk && row >= chunk.length) row = chunk.length - 1;
return [row, col];
};
const handleCursorMove = (event) => {
if (event.ctrlKey) {
switch (event.keyCode) {
case keycodes.leftArrow:
return checkMoveCursorBounds(currentCell[0], 0);
case keycodes.rightArrow:
return checkMoveCursorBounds(currentCell[0], columnChunks.length * 2 - 1);
}
}
switch (event.keyCode) {
case keycodes.leftArrow:
return checkMoveCursorBounds(currentCell[0], currentCell[1] - 1);
case keycodes.rightArrow:
return checkMoveCursorBounds(currentCell[0], currentCell[1] + 1);
case keycodes.upArrow:
return checkMoveCursorBounds(currentCell[0] - 1, currentCell[1]);
case keycodes.downArrow:
return checkMoveCursorBounds(currentCell[0] + 1, currentCell[1]);
case keycodes.pageUp:
return checkMoveCursorBounds(0, currentCell[1]);
case keycodes.pageDown:
return checkMoveCursorBounds(rowCount - 1, currentCell[1]);
case keycodes.home:
return checkMoveCursorBounds(0, 0);
case keycodes.end:
return checkMoveCursorBounds(rowCount - 1, columnChunks.length * 2 - 1);
}
};
const handleKeyNavigation = (event) => {
if (event.ctrlKey) {
switch (event.keyCode) {
case keycodes.upArrow:
return 'previous';
case keycodes.downArrow:
return 'next';
case keycodes.home:
return 'begin';
case keycodes.end:
return 'end';
}
}
};
function handleSave() {
if (inplaceEditorState.cell) {
// @ts-ignore
dispatchInsplaceEditor({ type: 'shouldSave' });
return;
}
if (onSave) onSave();
}
function getCellColumn(cell) {
const chunk = columnChunks[Math.floor(cell[1] / 2)];
if (!chunk) return;
const column = chunk[cell[0]];
return column;
}
function setCellValue(cell, value) {
const column = getCellColumn(cell);
if (!column) return;
former.setCellValue(column.uniqueName, value);
}
function setNull() {
if (isDataCell(currentCell)) {
setCellValue(currentCell, null);
}
}
const scrollIntoView = (cell) => {
const element = cellRefs.current[`${cell[0]},${cell[1]}`];
if (element) element.scrollIntoView();
};
React.useEffect(() => {
scrollIntoView(currentCell);
}, [rowData]);
const moveCurrentCell = (row, col) => {
const moved = checkMoveCursorBounds(row, col);
setCurrentCell(moved);
scrollIntoView(moved);
};
function copyToClipboard() {
const column = getCellColumn(currentCell);
if (!column) return;
const text = currentCell[1] % 2 == 1 ? rowData[column.uniqueName] : column.columnName;
copyTextToClipboard(text);
}
const handleKeyDown = (event) => {
const navigation = handleKeyNavigation(event);
if (navigation) {
event.preventDefault();
onNavigate(navigation);
return;
}
const moved = handleCursorMove(event);
if (moved) {
setCurrentCell(moved);
scrollIntoView(moved);
event.preventDefault();
return;
}
if (event.keyCode == keycodes.s && event.ctrlKey) {
event.preventDefault();
handleSave();
// this.saveAndFocus();
}
if (event.keyCode == keycodes.n0 && event.ctrlKey) {
event.preventDefault();
setNull();
}
if (event.keyCode == keycodes.r && event.ctrlKey) {
event.preventDefault();
former.revertRowChanges();
}
// if (event.keyCode == keycodes.f && event.ctrlKey) {
// event.preventDefault();
// filterSelectedValue();
// }
if (event.keyCode == keycodes.z && event.ctrlKey) {
event.preventDefault();
former.undo();
}
if (event.keyCode == keycodes.y && event.ctrlKey) {
event.preventDefault();
former.redo();
}
if (event.keyCode == keycodes.c && event.ctrlKey) {
event.preventDefault();
copyToClipboard();
}
if (event.keyCode == keycodes.f5) {
event.preventDefault();
onReload();
}
if (event.keyCode == keycodes.f4) {
event.preventDefault();
handleSwitchToTable();
}
if (
!event.ctrlKey &&
!event.altKey &&
((event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) ||
(event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) ||
event.keyCode == keycodes.dash)
) {
// @ts-ignore
dispatchInsplaceEditor({ type: 'show', text: event.nativeEvent.key, cell: currentCell });
return;
}
if (event.keyCode == keycodes.f2) {
// @ts-ignore
dispatchInsplaceEditor({ type: 'show', cell: currentCell, selectAll: true });
return;
}
};
const handleTableMouseDown = (event) => {
event.preventDefault();
if (focusFieldRef.current) focusFieldRef.current.focus();
if (event.target.closest('.buttonLike')) return;
if (event.target.closest('.resizeHandleControl')) return;
if (event.target.closest('input')) return;
// event.target.closest('table').focus();
event.preventDefault();
if (focusFieldRef.current) focusFieldRef.current.focus();
const cell = cellFromEvent(event);
if (isDataCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, currentCell)) {
// @ts-ignore
dispatchInsplaceEditor({ type: 'show', cell, selectAll: true });
} else if (!_.isEqual(cell, inplaceEditorState.cell)) {
// @ts-ignore
dispatchInsplaceEditor({ type: 'close' });
}
// @ts-ignore
setCurrentCell(cell);
};
const getCellWidth = (row, col) => {
const element = cellRefs.current[`${row},${col}`];
if (element) return element.getBoundingClientRect().width;
return 100;
};
const rowCountInfo = React.useMemo(() => {
if (allRowCount == null || rowCountBefore == null) return 'Loading row count...';
return `Row: ${(rowCountBefore + 1).toLocaleString()} / ${allRowCount.toLocaleString()}`;
}, [rowCountBefore, allRowCount]);
const [inplaceEditorState, dispatchInsplaceEditor] = React.useReducer((state, action) => {
switch (action.type) {
case 'show':
// if (!grider.editable) return {};
return {
cell: action.cell,
text: action.text,
selectAll: action.selectAll,
};
case 'close': {
const [row, col] = currentCell || [];
if (focusFieldRef.current) focusFieldRef.current.focus();
// @ts-ignore
if (action.mode == 'enter' && row) setTimeout(() => moveCurrentCell(row + 1, col), 0);
// if (action.mode == 'save') setTimeout(handleSave, 0);
return {};
}
case 'shouldSave': {
return {
...state,
shouldSave: true,
};
}
}
return {};
}, {});
const toolbar =
toolbarPortalRef &&
toolbarPortalRef.current &&
tabVisible &&
ReactDOM.createPortal(
<FormViewToolbar
switchToTable={handleSwitchToTable}
onNavigate={onNavigate}
reload={onReload}
reconnect={onReconnect}
save={handleSave}
former={former}
/>,
toolbarPortalRef.current
);
if (!formDisplay || !formDisplay.isLoadedCorrectly) return toolbar;
return (
<Wrapper ref={wrapperRef} onContextMenu={handleContextMenu}>
{columnChunks.map((chunk, chunkIndex) => (
<Table key={chunkIndex} onMouseDown={handleTableMouseDown}>
{chunk.map((col, rowIndex) => (
<TableRow key={col.columnName} theme={theme} ref={headerRowRef} style={{ height: `${rowHeight}px` }}>
<TableHeaderCell
theme={theme}
data-row={rowIndex}
data-col={chunkIndex * 2}
// @ts-ignore
isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2}
ref={(element) => setCellRef(rowIndex, chunkIndex * 2, element)}
>
<ColumnLabel {...col} />
</TableHeaderCell>
<TableBodyCell
theme={theme}
data-row={rowIndex}
data-col={chunkIndex * 2 + 1}
// @ts-ignore
isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2 + 1}
isModifiedCell={rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)}
ref={(element) => setCellRef(rowIndex, chunkIndex * 2 + 1, element)}
>
{inplaceEditorState.cell &&
rowIndex == inplaceEditorState.cell[0] &&
chunkIndex * 2 + 1 == inplaceEditorState.cell[1] ? (
<InplaceEditor
widthPx={getCellWidth(rowIndex, chunkIndex * 2 + 1)}
inplaceEditorState={inplaceEditorState}
dispatchInsplaceEditor={dispatchInsplaceEditor}
cellValue={rowData[col.uniqueName]}
onSetValue={(value) => {
former.setCellValue(col.uniqueName, value);
}}
// grider={grider}
// rowIndex={rowIndex}
// uniqueName={col.uniqueName}
/>
) : (
<>
<CellFormattedValue value={rowData && rowData[col.columnName]} dataType={col.dataType} />
{!!col.hintColumnName &&
rowData &&
!(rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)) && (
<HintSpan>{rowData[col.hintColumnName]}</HintSpan>
)}
</>
)}
</TableBodyCell>
</TableRow>
))}
</Table>
))}
<FocusField type="text" ref={focusFieldRef} onKeyDown={handleKeyDown} />
{rowCountInfo && <RowCountLabel theme={theme}>{rowCountInfo}</RowCountLabel>}
{toolbar}
</Wrapper>
);
}

View File

@ -0,0 +1,25 @@
import React from 'react';
import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu';
export default function FormViewContextMenu({ switchToTable, onNavigate }) {
return (
<>
<DropDownMenuItem onClick={switchToTable} keyText="F4">
Table view
</DropDownMenuItem>
<DropDownMenuDivider />
<DropDownMenuItem onClick={() => onNavigate('begin')} keyText="Ctrl+Home">
Navigate to begin
</DropDownMenuItem>
<DropDownMenuItem onClick={() => onNavigate('previous')} keyText="Ctrl+Up">
Navigate to previous
</DropDownMenuItem>
<DropDownMenuItem onClick={() => onNavigate('next')} keyText="Ctrl+Down">
Navigate to next
</DropDownMenuItem>
<DropDownMenuItem onClick={() => onNavigate('end')} keyText="Ctrl+End">
Navigate to end
</DropDownMenuItem>
</>
);
}

View File

@ -0,0 +1,42 @@
import React from 'react';
import ToolbarButton from '../widgets/ToolbarButton';
export default function FormViewToolbar({ switchToTable, onNavigate, reload, reconnect, former, save }) {
return (
<>
<ToolbarButton onClick={switchToTable} icon="icon table">
Table view
</ToolbarButton>
<ToolbarButton onClick={() => onNavigate('begin')} icon="icon arrow-begin">
First
</ToolbarButton>
<ToolbarButton onClick={() => onNavigate('previous')} icon="icon arrow-left">
Previous
</ToolbarButton>
<ToolbarButton onClick={() => onNavigate('next')} icon="icon arrow-right">
Next
</ToolbarButton>
<ToolbarButton onClick={() => onNavigate('end')} icon="icon arrow-end">
Last
</ToolbarButton>
<ToolbarButton onClick={reload} icon="icon reload">
Refresh
</ToolbarButton>
<ToolbarButton onClick={reconnect} icon="icon connection">
Reconnect
</ToolbarButton>
<ToolbarButton disabled={!former.canUndo} onClick={() => former.undo()} icon="icon undo">
Undo
</ToolbarButton>
<ToolbarButton disabled={!former.canRedo} onClick={() => former.redo()} icon="icon redo">
Redo
</ToolbarButton>
<ToolbarButton disabled={!former.allowSave} onClick={save} icon="icon save">
Save
</ToolbarButton>
<ToolbarButton disabled={!former.containsChanges} onClick={() => former.revertAllChanges()} icon="icon close">
Revert
</ToolbarButton>
</>
);
}

View File

@ -0,0 +1,53 @@
// export interface GriderRowStatus {
// status: 'regular' | 'updated' | 'deleted' | 'inserted';
// modifiedFields?: Set<string>;
// insertedFields?: Set<string>;
// deletedFields?: Set<string>;
// }
export default abstract class Former {
public rowData: any;
// getRowStatus(index): GriderRowStatus {
// const res: GriderRowStatus = {
// status: 'regular',
// };
// return res;
// }
beginUpdate() {}
endUpdate() {}
setCellValue(uniqueName: string, value: any) {}
revertRowChanges() {}
revertAllChanges() {}
undo() {}
redo() {}
get editable() {
return false;
}
get canInsert() {
return false;
}
get allowSave() {
return this.containsChanges;
}
get canUndo() {
return false;
}
get canRedo() {
return false;
}
get containsChanges() {
return false;
}
get disableLoadNextPage() {
return false;
}
get errors() {
return null;
}
updateRow(changeObject) {
for (const key of Object.keys(changeObject)) {
this.setCellValue(key, changeObject[key]);
}
}
}

View File

@ -0,0 +1,180 @@
import { changeSetToSql, createChangeSet, TableFormViewDisplay } from 'dbgate-datalib';
import { findEngineDriver } from 'dbgate-tools';
import React from 'react';
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
import useExtensions from '../utility/useExtensions';
import FormView from './FormView';
import axios from '../utility/axios';
import ChangeSetFormer from './ChangeSetFormer';
import ConfirmSqlModal from '../modals/ConfirmSqlModal';
import ErrorMessageModal from '../modals/ErrorMessageModal';
import { scriptToSql } from 'dbgate-sqltree';
import useModalState from '../modals/useModalState';
import useShowModal from '../modals/showModal';
async function loadRow(props, sql) {
const { conid, database } = props;
if (!sql) return null;
const response = await axios.request({
url: 'database-connections/query-data',
method: 'post',
params: {
conid,
database,
},
data: { sql },
});
if (response.data.errorMessage) return response.data;
return response.data.rows[0];
}
export default function SqlFormView(props) {
const { formDisplay, changeSetState, dispatchChangeSet, conid, database, onReferenceSourceChanged } = props;
const [rowData, setRowData] = React.useState(null);
const [reloadToken, setReloadToken] = React.useState(0);
const [rowCountInfo, setRowCountInfo] = React.useState(null);
const confirmSqlModalState = useModalState();
const [confirmSql, setConfirmSql] = React.useState('');
const showModal = useShowModal();
const changeSet = changeSetState && changeSetState.value;
const changeSetRef = React.useRef(changeSet);
changeSetRef.current = changeSet;
const handleLoadCurrentRow = async () => {
const row = await loadRow(props, formDisplay.getCurrentRowQuery());
if (row) setRowData(row);
};
const handleLoadRowCount = async () => {
const countRow = await loadRow(props, formDisplay.getCountQuery());
const countBeforeRow = await loadRow(props, formDisplay.getBeforeCountQuery());
if (countRow && countBeforeRow) {
setRowCountInfo({
allRowCount: parseInt(countRow.count),
rowCountBefore: parseInt(countBeforeRow.count),
});
}
};
const handleNavigate = async (command) => {
const row = await loadRow(props, formDisplay.navigateRowQuery(command));
if (row) {
setRowData(row);
formDisplay.navigate(row);
}
};
React.useEffect(() => {
if (onReferenceSourceChanged && rowData) onReferenceSourceChanged([rowData]);
}, [rowData]);
React.useEffect(() => {
if (formDisplay) handleLoadCurrentRow();
setRowCountInfo(null);
handleLoadRowCount();
}, [reloadToken]);
React.useEffect(() => {
if (!formDisplay.isLoadedCorrectly) return;
if (formDisplay && !formDisplay.isLoadedCurrentRow(rowData)) {
handleLoadCurrentRow();
}
setRowCountInfo(null);
handleLoadRowCount();
}, [formDisplay]);
const former = React.useMemo(() => new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay), [
rowData,
changeSetState,
dispatchChangeSet,
formDisplay,
]);
function handleSave() {
const script = changeSetToSql(changeSetRef.current, formDisplay.dbinfo);
const sql = scriptToSql(formDisplay.driver, script);
setConfirmSql(sql);
confirmSqlModalState.open();
}
async function handleConfirmSql() {
const resp = await axios.request({
url: 'database-connections/query-data',
method: 'post',
params: {
conid,
database,
},
data: { sql: confirmSql },
});
const { errorMessage } = resp.data || {};
if (errorMessage) {
showModal((modalState) => (
<ErrorMessageModal modalState={modalState} message={errorMessage} title="Error when saving" />
));
} else {
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
setConfirmSql(null);
setReloadToken((x) => x + 1);
}
}
// const { config, setConfig, cache, setCache, schemaName, pureName, conid, database } = props;
// const { formViewKey } = config;
// const [display, setDisplay] = React.useState(null);
// const connection = useConnectionInfo({ conid });
// const dbinfo = useDatabaseInfo({ conid, database });
// const extensions = useExtensions();
// console.log('SqlFormView.props', props);
// React.useEffect(() => {
// const newDisplay = connection
// ? new TableFormViewDisplay(
// { schemaName, pureName },
// findEngineDriver(connection, extensions),
// config,
// setConfig,
// cache,
// setCache,
// dbinfo
// )
// : null;
// if (!newDisplay) return;
// if (display && display.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return;
// setDisplay(newDisplay);
// }, [config, cache, conid, database, schemaName, pureName, dbinfo, extensions]);
return (
<>
<FormView
{...props}
rowData={rowData}
onNavigate={handleNavigate}
former={former}
onSave={handleSave}
onReload={() => setReloadToken((x) => x + 1)}
onReconnect={async () => {
await axios.post('database-connections/refresh', { conid, database });
formDisplay.reload();
}}
{...rowCountInfo}
/>
<ConfirmSqlModal
modalState={confirmSqlModalState}
sql={confirmSql}
engine={formDisplay.engine}
onConfirm={handleConfirmSql}
/>
</>
);
}

View File

@ -32,12 +32,15 @@ const iconNames = {
'icon web': 'mdi mdi-web',
'icon home': 'mdi mdi-home',
'icon query-design': 'mdi mdi-vector-polyline-edit',
'icon form': 'mdi mdi-form-select',
'icon edit': 'mdi mdi-pencil',
'icon delete': 'mdi mdi-delete',
'icon arrow-up': 'mdi mdi-arrow-up',
'icon arrow-down': 'mdi mdi-arrow-down',
'icon arrow-left': 'mdi mdi-arrow-left',
'icon arrow-begin': 'mdi mdi-arrow-collapse-left',
'icon arrow-end': 'mdi mdi-arrow-collapse-right',
'icon arrow-right': 'mdi mdi-arrow-right',
'icon format-code': 'mdi mdi-code-tags-check',
'icon show-wizard': 'mdi mdi-comment-edit',