diff --git a/packages/datalib/src/TableFormViewDisplay.ts b/packages/datalib/src/TableFormViewDisplay.ts
index 4a26cd646..eda82f1fd 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 0ad1fbeed..5c9ca3b05 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 a0d070f0b..d418a684a 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 326a23851..7a2b4a3ea 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}
+ />