mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-30 16:13:58 +00:00
form view - row count
This commit is contained in:
@@ -42,7 +42,7 @@ export class TableFormViewDisplay extends FormViewDisplay {
|
|||||||
if (!row) row = this.config.formViewKey;
|
if (!row) row = this.config.formViewKey;
|
||||||
if (!row) return null;
|
if (!row) return null;
|
||||||
const { primaryKey } = this.gridDisplay.baseTable;
|
const { primaryKey } = this.gridDisplay.baseTable;
|
||||||
if (primaryKey) return null;
|
if (!primaryKey) return null;
|
||||||
return {
|
return {
|
||||||
conditionType: 'and',
|
conditionType: 'and',
|
||||||
conditions: primaryKey.columns.map(({ columnName }) => ({
|
conditions: primaryKey.columns.map(({ columnName }) => ({
|
||||||
@@ -68,6 +68,7 @@ export class TableFormViewDisplay extends FormViewDisplay {
|
|||||||
const conditions = [];
|
const conditions = [];
|
||||||
|
|
||||||
const { primaryKey } = this.gridDisplay.baseTable;
|
const { primaryKey } = this.gridDisplay.baseTable;
|
||||||
|
if (!primaryKey) return null;
|
||||||
for (let index = 0; index < primaryKey.columns.length; index++) {
|
for (let index = 0; index < primaryKey.columns.length; index++) {
|
||||||
conditions.push({
|
conditions.push({
|
||||||
conditionType: 'and',
|
conditionType: 'and',
|
||||||
@@ -133,7 +134,40 @@ export class TableFormViewDisplay extends FormViewDisplay {
|
|||||||
return sql;
|
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) {
|
extractKey(row) {
|
||||||
|
if (!row || !this.gridDisplay.baseTable || !this.gridDisplay.baseTable.primaryKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const formViewKey = _.pick(
|
const formViewKey = _.pick(
|
||||||
row,
|
row,
|
||||||
this.gridDisplay.baseTable.primaryKey.columns.map((x) => x.columnName)
|
this.gridDisplay.baseTable.primaryKey.columns.map((x) => x.columnName)
|
||||||
@@ -150,6 +184,7 @@ export class TableFormViewDisplay extends FormViewDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoadedCurrentRow(row) {
|
isLoadedCurrentRow(row) {
|
||||||
|
console.log('isLoadedCurrentRow', row, this.config.formViewKey);
|
||||||
if (!row) return false;
|
if (!row) return false;
|
||||||
const formViewKey = this.extractKey(row);
|
const formViewKey = this.extractKey(row);
|
||||||
return stableStringify(formViewKey) == stableStringify(this.config.formViewKey);
|
return stableStringify(formViewKey) == stableStringify(this.config.formViewKey);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import keycodes from '../utility/keycodes';
|
|||||||
import { CellFormattedValue } from '../datagrid/DataGridRow';
|
import { CellFormattedValue } from '../datagrid/DataGridRow';
|
||||||
import { cellFromEvent } from '../datagrid/selection';
|
import { cellFromEvent } from '../datagrid/selection';
|
||||||
import InplaceEditor from '../datagrid/InplaceEditor';
|
import InplaceEditor from '../datagrid/InplaceEditor';
|
||||||
|
import { copyTextToClipboard } from '../utility/clipboard';
|
||||||
|
|
||||||
const Table = styled.table`
|
const Table = styled.table`
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -87,12 +88,33 @@ const FocusField = styled.input`
|
|||||||
top: -1000px;
|
top: -1000px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const RowCountLabel = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
background-color: ${(props) => props.theme.gridbody_background_yellow[1]};
|
||||||
|
right: 40px;
|
||||||
|
bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
function isDataCell(cell) {
|
function isDataCell(cell) {
|
||||||
return cell[1] % 2 == 1;
|
return cell[1] % 2 == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormView(props) {
|
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} */
|
/** @type {import('dbgate-datalib').FormViewDisplay} */
|
||||||
const formDisplay = props.formDisplay;
|
const formDisplay = props.formDisplay;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -197,6 +219,25 @@ export default function FormView(props) {
|
|||||||
if (onSave) onSave();
|
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 scrollIntoView = (cell) => {
|
||||||
const element = cellRefs.current[`${cell[0]},${cell[1]}`];
|
const element = cellRefs.current[`${cell[0]},${cell[1]}`];
|
||||||
if (element) element.scrollIntoView();
|
if (element) element.scrollIntoView();
|
||||||
@@ -212,6 +253,13 @@ export default function FormView(props) {
|
|||||||
scrollIntoView(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 handleKeyDown = (event) => {
|
||||||
const navigation = handleKeyNavigation(event);
|
const navigation = handleKeyNavigation(event);
|
||||||
if (navigation) {
|
if (navigation) {
|
||||||
@@ -232,6 +280,36 @@ export default function FormView(props) {
|
|||||||
// this.saveAndFocus();
|
// 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 (
|
if (
|
||||||
!event.ctrlKey &&
|
!event.ctrlKey &&
|
||||||
!event.altKey &&
|
!event.altKey &&
|
||||||
@@ -281,6 +359,11 @@ export default function FormView(props) {
|
|||||||
return 100;
|
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) => {
|
const [inplaceEditorState, dispatchInsplaceEditor] = React.useReducer((state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'show':
|
case 'show':
|
||||||
@@ -313,7 +396,14 @@ export default function FormView(props) {
|
|||||||
toolbarPortalRef.current &&
|
toolbarPortalRef.current &&
|
||||||
tabVisible &&
|
tabVisible &&
|
||||||
ReactDOM.createPortal(
|
ReactDOM.createPortal(
|
||||||
<FormViewToolbar switchToTable={handleSwitchToTable} onNavigate={onNavigate} />,
|
<FormViewToolbar
|
||||||
|
switchToTable={handleSwitchToTable}
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
reload={onReload}
|
||||||
|
reconnect={onReconnect}
|
||||||
|
save={handleSave}
|
||||||
|
former={former}
|
||||||
|
/>,
|
||||||
toolbarPortalRef.current
|
toolbarPortalRef.current
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -371,6 +461,7 @@ export default function FormView(props) {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<FocusField type="text" ref={focusFieldRef} onKeyDown={handleKeyDown} />
|
<FocusField type="text" ref={focusFieldRef} onKeyDown={handleKeyDown} />
|
||||||
|
{rowCountInfo && <RowCountLabel theme={theme}>{rowCountInfo}</RowCountLabel>}
|
||||||
|
|
||||||
{toolbar}
|
{toolbar}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function FormViewToolbar({ switchToTable, onNavigate }) {
|
export default function FormViewToolbar({ switchToTable, onNavigate, reload, reconnect, former, save }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolbarButton onClick={switchToTable} icon="icon table">
|
<ToolbarButton onClick={switchToTable} icon="icon table">
|
||||||
@@ -19,6 +19,24 @@ export default function FormViewToolbar({ switchToTable, onNavigate }) {
|
|||||||
<ToolbarButton onClick={() => onNavigate('end')} icon="icon arrow-end">
|
<ToolbarButton onClick={() => onNavigate('end')} icon="icon arrow-end">
|
||||||
Last
|
Last
|
||||||
</ToolbarButton>
|
</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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import useShowModal from '../modals/showModal';
|
|||||||
|
|
||||||
async function loadRow(props, sql) {
|
async function loadRow(props, sql) {
|
||||||
const { conid, database } = props;
|
const { conid, database } = props;
|
||||||
/** @type {import('dbgate-datalib').TableFormViewDisplay} */
|
|
||||||
const formDisplay = props.formDisplay;
|
if (!sql) return null;
|
||||||
|
|
||||||
const response = await axios.request({
|
const response = await axios.request({
|
||||||
url: 'database-connections/query-data',
|
url: 'database-connections/query-data',
|
||||||
@@ -35,6 +35,7 @@ export default function SqlFormView(props) {
|
|||||||
const { formDisplay, changeSetState, dispatchChangeSet, conid, database } = props;
|
const { formDisplay, changeSetState, dispatchChangeSet, conid, database } = props;
|
||||||
const [rowData, setRowData] = React.useState(null);
|
const [rowData, setRowData] = React.useState(null);
|
||||||
const [reloadToken, setReloadToken] = React.useState(0);
|
const [reloadToken, setReloadToken] = React.useState(0);
|
||||||
|
const [rowCountInfo, setRowCountInfo] = React.useState(null);
|
||||||
|
|
||||||
const confirmSqlModalState = useModalState();
|
const confirmSqlModalState = useModalState();
|
||||||
const [confirmSql, setConfirmSql] = React.useState('');
|
const [confirmSql, setConfirmSql] = React.useState('');
|
||||||
@@ -49,6 +50,18 @@ export default function SqlFormView(props) {
|
|||||||
if (row) setRowData(row);
|
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 handleNavigate = async (command) => {
|
||||||
const row = await loadRow(props, formDisplay.navigateRowQuery(command));
|
const row = await loadRow(props, formDisplay.navigateRowQuery(command));
|
||||||
if (row) {
|
if (row) {
|
||||||
@@ -59,13 +72,19 @@ export default function SqlFormView(props) {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (formDisplay) handleLoadCurrentRow();
|
if (formDisplay) handleLoadCurrentRow();
|
||||||
|
setRowCountInfo(null);
|
||||||
|
handleLoadRowCount();
|
||||||
}, [reloadToken]);
|
}, [reloadToken]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (!formDisplay.isLoadedCorrectly) return;
|
||||||
|
|
||||||
if (formDisplay && !formDisplay.isLoadedCurrentRow(rowData)) {
|
if (formDisplay && !formDisplay.isLoadedCurrentRow(rowData)) {
|
||||||
handleLoadCurrentRow();
|
handleLoadCurrentRow();
|
||||||
}
|
}
|
||||||
}, [formDisplay, rowData]);
|
setRowCountInfo(null);
|
||||||
|
handleLoadRowCount();
|
||||||
|
}, [formDisplay]);
|
||||||
|
|
||||||
const former = React.useMemo(() => new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay), [
|
const former = React.useMemo(() => new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay), [
|
||||||
rowData,
|
rowData,
|
||||||
@@ -133,7 +152,19 @@ export default function SqlFormView(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormView {...props} rowData={rowData} onNavigate={handleNavigate} former={former} onSave={handleSave} />
|
<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
|
<ConfirmSqlModal
|
||||||
modalState={confirmSqlModalState}
|
modalState={confirmSqlModalState}
|
||||||
sql={confirmSql}
|
sql={confirmSql}
|
||||||
|
|||||||
Reference in New Issue
Block a user