From 9d50ac00933bc592b45f3e2b744557063e495cb6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 11 Jan 2021 17:25:05 +0100 Subject: [PATCH] form view - row count --- packages/datalib/src/TableFormViewDisplay.ts | 37 +++++++- packages/web/src/formview/FormView.js | 95 +++++++++++++++++++- packages/web/src/formview/FormViewToolbar.js | 20 ++++- packages/web/src/formview/SqlFormView.js | 39 +++++++- 4 files changed, 183 insertions(+), 8 deletions(-) diff --git a/packages/datalib/src/TableFormViewDisplay.ts b/packages/datalib/src/TableFormViewDisplay.ts index 4a26cd64..eda82f1f 100644 --- a/packages/datalib/src/TableFormViewDisplay.ts +++ b/packages/datalib/src/TableFormViewDisplay.ts @@ -42,7 +42,7 @@ export class TableFormViewDisplay extends FormViewDisplay { if (!row) row = this.config.formViewKey; if (!row) return null; const { primaryKey } = this.gridDisplay.baseTable; - if (primaryKey) return null; + if (!primaryKey) return null; return { conditionType: 'and', conditions: primaryKey.columns.map(({ columnName }) => ({ @@ -68,6 +68,7 @@ export class TableFormViewDisplay extends FormViewDisplay { const conditions = []; const { primaryKey } = this.gridDisplay.baseTable; + if (!primaryKey) return null; for (let index = 0; index < primaryKey.columns.length; index++) { conditions.push({ conditionType: 'and', @@ -133,7 +134,40 @@ export class TableFormViewDisplay extends FormViewDisplay { 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(); + const sql = treeToSql(this.driver, select, dumpSqlSelect); + return sql; + } + + getBeforeCountQuery() { + if (!this.driver) return null; + const select = this.getCountSelect(); + 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) @@ -150,6 +184,7 @@ export class TableFormViewDisplay extends FormViewDisplay { } 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); diff --git a/packages/web/src/formview/FormView.js b/packages/web/src/formview/FormView.js index 0ad1fbee..5c9ca3b0 100644 --- a/packages/web/src/formview/FormView.js +++ b/packages/web/src/formview/FormView.js @@ -15,6 +15,7 @@ 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; @@ -87,12 +88,33 @@ const FocusField = styled.input` top: -1000px; `; +const RowCountLabel = styled.div` + position: absolute; + background-color: ${(props) => props.theme.gridbody_background_yellow[1]}; + right: 40px; + bottom: 20px; +`; + function isDataCell(cell) { return cell[1] % 2 == 1; } export default function FormView(props) { - const { toolbarPortalRef, tabVisible, config, setConfig, onNavigate, former, onSave } = props; + const { + toolbarPortalRef, + tabVisible, + config, + setConfig, + onNavigate, + former, + onSave, + conid, + database, + onReload, + onReconnect, + allRowCount, + rowCountBefore, + } = props; /** @type {import('dbgate-datalib').FormViewDisplay} */ const formDisplay = props.formDisplay; const theme = useTheme(); @@ -197,6 +219,25 @@ export default function FormView(props) { 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(); @@ -212,6 +253,13 @@ export default function FormView(props) { 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) { @@ -232,6 +280,36 @@ export default function FormView(props) { // 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.ctrlKey && !event.altKey && @@ -281,6 +359,11 @@ export default function FormView(props) { 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': @@ -313,7 +396,14 @@ export default function FormView(props) { toolbarPortalRef.current && tabVisible && ReactDOM.createPortal( - , + , toolbarPortalRef.current ); @@ -371,6 +461,7 @@ export default function FormView(props) { ))} + {rowCountInfo && {rowCountInfo}} {toolbar} diff --git a/packages/web/src/formview/FormViewToolbar.js b/packages/web/src/formview/FormViewToolbar.js index a0d070f0..d418a684 100644 --- a/packages/web/src/formview/FormViewToolbar.js +++ b/packages/web/src/formview/FormViewToolbar.js @@ -1,7 +1,7 @@ import React from 'react'; import ToolbarButton from '../widgets/ToolbarButton'; -export default function FormViewToolbar({ switchToTable, onNavigate }) { +export default function FormViewToolbar({ switchToTable, onNavigate, reload, reconnect, former, save }) { return ( <> @@ -19,6 +19,24 @@ export default function FormViewToolbar({ switchToTable, onNavigate }) { onNavigate('end')} icon="icon arrow-end"> Last + + Refresh + + + Reconnect + + former.undo()} icon="icon undo"> + Undo + + former.redo()} icon="icon redo"> + Redo + + + Save + + former.revertAllChanges()} icon="icon close"> + Revert + ); } diff --git a/packages/web/src/formview/SqlFormView.js b/packages/web/src/formview/SqlFormView.js index 326a2385..7a2b4a3e 100644 --- a/packages/web/src/formview/SqlFormView.js +++ b/packages/web/src/formview/SqlFormView.js @@ -14,8 +14,8 @@ import useShowModal from '../modals/showModal'; async function loadRow(props, sql) { const { conid, database } = props; - /** @type {import('dbgate-datalib').TableFormViewDisplay} */ - const formDisplay = props.formDisplay; + + if (!sql) return null; const response = await axios.request({ url: 'database-connections/query-data', @@ -35,6 +35,7 @@ export default function SqlFormView(props) { const { formDisplay, changeSetState, dispatchChangeSet, conid, database } = 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(''); @@ -49,6 +50,18 @@ export default function SqlFormView(props) { 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) { @@ -59,13 +72,19 @@ export default function SqlFormView(props) { React.useEffect(() => { if (formDisplay) handleLoadCurrentRow(); + setRowCountInfo(null); + handleLoadRowCount(); }, [reloadToken]); React.useEffect(() => { + if (!formDisplay.isLoadedCorrectly) return; + if (formDisplay && !formDisplay.isLoadedCurrentRow(rowData)) { handleLoadCurrentRow(); } - }, [formDisplay, rowData]); + setRowCountInfo(null); + handleLoadRowCount(); + }, [formDisplay]); const former = React.useMemo(() => new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay), [ rowData, @@ -133,7 +152,19 @@ export default function SqlFormView(props) { return ( <> - + setReloadToken((x) => x + 1)} + onReconnect={async () => { + await axios.post('database-connections/refresh', { conid, database }); + formDisplay.reload(); + }} + {...rowCountInfo} + />