mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
form view - row count
This commit is contained in:
parent
7c8c7467e1
commit
9d50ac0093
@ -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);
|
||||
|
@ -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(
|
||||
<FormViewToolbar switchToTable={handleSwitchToTable} onNavigate={onNavigate} />,
|
||||
<FormViewToolbar
|
||||
switchToTable={handleSwitchToTable}
|
||||
onNavigate={onNavigate}
|
||||
reload={onReload}
|
||||
reconnect={onReconnect}
|
||||
save={handleSave}
|
||||
former={former}
|
||||
/>,
|
||||
toolbarPortalRef.current
|
||||
);
|
||||
|
||||
@ -371,6 +461,7 @@ export default function FormView(props) {
|
||||
))}
|
||||
|
||||
<FocusField type="text" ref={focusFieldRef} onKeyDown={handleKeyDown} />
|
||||
{rowCountInfo && <RowCountLabel theme={theme}>{rowCountInfo}</RowCountLabel>}
|
||||
|
||||
{toolbar}
|
||||
</Wrapper>
|
||||
|
@ -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 (
|
||||
<>
|
||||
<ToolbarButton onClick={switchToTable} icon="icon table">
|
||||
@ -19,6 +19,24 @@ export default function FormViewToolbar({ switchToTable, onNavigate }) {
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<>
|
||||
<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
|
||||
modalState={confirmSqlModalState}
|
||||
sql={confirmSql}
|
||||
|
Loading…
Reference in New Issue
Block a user