mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 12:06:00 +00:00
save dialog
This commit is contained in:
@@ -98,9 +98,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
td.isSelected {
|
|
||||||
background: var(--theme-bg-selected);
|
|
||||||
}
|
|
||||||
td.isFrameSelected {
|
td.isFrameSelected {
|
||||||
outline: 3px solid var(--theme-bg-selected);
|
outline: 3px solid var(--theme-bg-selected);
|
||||||
outline-offset: -3px;
|
outline-offset: -3px;
|
||||||
@@ -127,6 +124,9 @@
|
|||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
}
|
}
|
||||||
|
td.isSelected {
|
||||||
|
background: var(--theme-bg-selected);
|
||||||
|
}
|
||||||
|
|
||||||
.hint {
|
.hint {
|
||||||
color: var(--theme-font-3);
|
color: var(--theme-font-3);
|
||||||
|
|||||||
@@ -12,6 +12,17 @@
|
|||||||
onClick: () => get(currentDataGrid).refresh(),
|
onClick: () => get(currentDataGrid).refresh(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'dataGrid.save',
|
||||||
|
category: 'Data grid',
|
||||||
|
name: 'Save',
|
||||||
|
keyText: 'Ctrl+S',
|
||||||
|
toolbar: true,
|
||||||
|
icon: 'icon save',
|
||||||
|
enabledStore: derived([currentDataGrid], ([grid]) => grid?.getGrider()?.allowSave),
|
||||||
|
onClick: () => get(currentDataGrid).save(),
|
||||||
|
});
|
||||||
|
|
||||||
function getRowCountInfo(selectedCells, grider, realColumnUniqueNames, selectedRowData, allRowCount) {
|
function getRowCountInfo(selectedCells, grider, realColumnUniqueNames, selectedRowData, allRowCount) {
|
||||||
if (selectedCells.length > 1 && selectedCells.every(x => _.isNumber(x[0]) && _.isNumber(x[1]))) {
|
if (selectedCells.length > 1 && selectedCells.every(x => _.isNumber(x[0]) && _.isNumber(x[1]))) {
|
||||||
let sum = _.sumBy(selectedCells, cell => {
|
let sum = _.sumBy(selectedCells, cell => {
|
||||||
@@ -62,6 +73,9 @@
|
|||||||
import InlineButton from '../elements/InlineButton.svelte';
|
import InlineButton from '../elements/InlineButton.svelte';
|
||||||
import FontIcon from '../icons/FontIcon.svelte';
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
import DataFilterControl from './DataFilterControl.svelte';
|
import DataFilterControl from './DataFilterControl.svelte';
|
||||||
|
import createReducer from '../utility/createReducer';
|
||||||
|
import keycodes from '../utility/keycodes';
|
||||||
|
import { nullStore } from '../stores';
|
||||||
|
|
||||||
export let loadNextData = undefined;
|
export let loadNextData = undefined;
|
||||||
export let grider = undefined;
|
export let grider = undefined;
|
||||||
@@ -73,6 +87,7 @@
|
|||||||
export let allRowCount = undefined;
|
export let allRowCount = undefined;
|
||||||
export let onReferenceSourceChanged = undefined;
|
export let onReferenceSourceChanged = undefined;
|
||||||
export let onReferenceClick = undefined;
|
export let onReferenceClick = undefined;
|
||||||
|
export let onSave;
|
||||||
|
|
||||||
export let isLoadedAll;
|
export let isLoadedAll;
|
||||||
export let loadedTime;
|
export let loadedTime;
|
||||||
@@ -97,6 +112,18 @@
|
|||||||
let autofillDragStartCell = nullCell;
|
let autofillDragStartCell = nullCell;
|
||||||
let autofillSelectedCells = emptyCellArray;
|
let autofillSelectedCells = emptyCellArray;
|
||||||
|
|
||||||
|
export function refresh() {
|
||||||
|
display.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function save() {
|
||||||
|
if (onSave) onSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGrider() {
|
||||||
|
return grider;
|
||||||
|
}
|
||||||
|
|
||||||
$: autofillMarkerCell =
|
$: autofillMarkerCell =
|
||||||
selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1
|
selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1
|
||||||
? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))]
|
? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))]
|
||||||
@@ -228,6 +255,7 @@
|
|||||||
if (autofill) {
|
if (autofill) {
|
||||||
autofillDragStartCell = cell;
|
autofillDragStartCell = cell;
|
||||||
} else {
|
} else {
|
||||||
|
const oldCurrentCell = currentCell;
|
||||||
currentCell = cell;
|
currentCell = cell;
|
||||||
|
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
@@ -242,13 +270,11 @@
|
|||||||
selectedCells = getCellRange(cell, cell);
|
selectedCells = getCellRange(cell, cell);
|
||||||
dragStartCell = cell;
|
dragStartCell = cell;
|
||||||
|
|
||||||
// if (isRegularCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, currentCell)) {
|
if (isRegularCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, oldCurrentCell)) {
|
||||||
// // @ts-ignore
|
dispatchInsplaceEditor({ type: 'show', cell, selectAll: true });
|
||||||
// dispatchInsplaceEditor({ type: 'show', cell, selectAll: true });
|
} else if (!_.isEqual(cell, inplaceEditorState.cell)) {
|
||||||
// } else if (!_.isEqual(cell, inplaceEditorState.cell)) {
|
dispatchInsplaceEditor({ type: 'close' });
|
||||||
// // @ts-ignore
|
}
|
||||||
// dispatchInsplaceEditor({ type: 'close' });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,10 +340,6 @@
|
|||||||
domVerticalScroll.scroll(newFirstVisibleRowScrollIndex);
|
domVerticalScroll.scroll(newFirstVisibleRowScrollIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refresh() {
|
|
||||||
display.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSelectedRowIndexes() {
|
function getSelectedRowIndexes() {
|
||||||
if (selectedCells.find(x => x[0] == 'header')) return _.range(0, grider.rowCount);
|
if (selectedCells.find(x => x[0] == 'header')) return _.range(0, grider.rowCount);
|
||||||
return _.uniq((selectedCells || []).map(x => x[0])).filter(x => _.isNumber(x));
|
return _.uniq((selectedCells || []).map(x => x[0])).filter(x => _.isNumber(x));
|
||||||
@@ -339,6 +361,206 @@
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleGridKeyDown(event) {
|
||||||
|
if (event.keyCode == keycodes.f5) {
|
||||||
|
event.preventDefault();
|
||||||
|
display.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.f4) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// handleSwitchToFormView();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// revertRowChanges();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.f && event.ctrlKey) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// filterSelectedValue();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.z && event.ctrlKey) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// undo();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.y && event.ctrlKey) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// redo();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.c && event.ctrlKey) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// copyToClipboard();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.delete && event.ctrlKey) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// deleteSelectedRows();
|
||||||
|
// // this.saveAndFocus();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (event.keyCode == keycodes.insert && !event.ctrlKey) {
|
||||||
|
// event.preventDefault();
|
||||||
|
// insertNewRow();
|
||||||
|
// // this.saveAndFocus();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if ($inplaceEditorState.cell) return;
|
||||||
|
|
||||||
|
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
|
||||||
|
event.preventDefault();
|
||||||
|
dispatchInsplaceEditor({ type: 'show', text: event.key, cell: currentCell });
|
||||||
|
// console.log('event', event.nativeEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.keyCode == keycodes.f2) {
|
||||||
|
// @ts-ignore
|
||||||
|
dispatchInsplaceEditor({ type: 'show', cell: currentCell, selectAll: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
if (!isRegularCell(shiftDragStartCell)) {
|
||||||
|
shiftDragStartCell = currentCell;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shiftDragStartCell = nullCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCursorMove(event);
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
selectedCells = getCellRange(shiftDragStartCell || currentCell, currentCell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCursorMove(event) {
|
||||||
|
if (!isRegularCell(currentCell)) return null;
|
||||||
|
let rowCount = grider.rowCount;
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case keycodes.upArrow:
|
||||||
|
case keycodes.pageUp:
|
||||||
|
return moveCurrentCell(0, currentCell[1], event);
|
||||||
|
case keycodes.downArrow:
|
||||||
|
case keycodes.pageDown:
|
||||||
|
return moveCurrentCell(rowCount - 1, currentCell[1], event);
|
||||||
|
case keycodes.leftArrow:
|
||||||
|
return moveCurrentCell(currentCell[0], 0, event);
|
||||||
|
case keycodes.rightArrow:
|
||||||
|
return moveCurrentCell(currentCell[0], columnSizes.realCount - 1, event);
|
||||||
|
case keycodes.home:
|
||||||
|
return moveCurrentCell(0, 0, event);
|
||||||
|
case keycodes.end:
|
||||||
|
return moveCurrentCell(rowCount - 1, columnSizes.realCount - 1, event);
|
||||||
|
case keycodes.a:
|
||||||
|
selectedCells = [['header', 'header']];
|
||||||
|
event.preventDefault();
|
||||||
|
return ['header', 'header'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case keycodes.upArrow:
|
||||||
|
// if (currentCell[0] == 0) return focusFilterEditor(currentCell[1]);
|
||||||
|
return moveCurrentCell(currentCell[0] - 1, currentCell[1], event);
|
||||||
|
case keycodes.downArrow:
|
||||||
|
case keycodes.enter:
|
||||||
|
return moveCurrentCell(currentCell[0] + 1, currentCell[1], event);
|
||||||
|
case keycodes.leftArrow:
|
||||||
|
return moveCurrentCell(currentCell[0], currentCell[1] - 1, event);
|
||||||
|
case keycodes.rightArrow:
|
||||||
|
return moveCurrentCell(currentCell[0], currentCell[1] + 1, event);
|
||||||
|
case keycodes.home:
|
||||||
|
return moveCurrentCell(currentCell[0], 0, event);
|
||||||
|
case keycodes.end:
|
||||||
|
return moveCurrentCell(currentCell[0], columnSizes.realCount - 1, event);
|
||||||
|
case keycodes.pageUp:
|
||||||
|
return moveCurrentCell(currentCell[0] - visibleRowCountLowerBound, currentCell[1], event);
|
||||||
|
case keycodes.pageDown:
|
||||||
|
return moveCurrentCell(currentCell[0] + visibleRowCountLowerBound, currentCell[1], event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNull() {
|
||||||
|
grider.beginUpdate();
|
||||||
|
selectedCells.filter(isRegularCell).forEach(cell => {
|
||||||
|
setCellValue(cell, null);
|
||||||
|
});
|
||||||
|
grider.endUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCellValue(cell, value) {
|
||||||
|
grider.setCellValue(cell[0], realColumnUniqueNames[cell[1]], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveCurrentCell(row, col, event = null) {
|
||||||
|
const rowCount = grider.rowCount;
|
||||||
|
|
||||||
|
if (row < 0) row = 0;
|
||||||
|
if (row >= rowCount) row = rowCount - 1;
|
||||||
|
if (col < 0) col = 0;
|
||||||
|
if (col >= columnSizes.realCount) col = columnSizes.realCount - 1;
|
||||||
|
currentCell = [row, col];
|
||||||
|
// setSelectedCells([...(event.ctrlKey ? selectedCells : []), [row, col]]);
|
||||||
|
selectedCells = [[row, col]];
|
||||||
|
scrollIntoView([row, col]);
|
||||||
|
// this.selectedCells.push(this.currentCell);
|
||||||
|
// this.scrollIntoView(this.currentCell);
|
||||||
|
|
||||||
|
if (event) event.preventDefault();
|
||||||
|
return [row, col];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [inplaceEditorState, dispatchInsplaceEditor] = createReducer((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 (domFocusField) domFocusField.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 {};
|
||||||
|
}, {});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container" bind:clientWidth={containerWidth} bind:clientHeight={containerHeight}>
|
<div class="container" bind:clientWidth={containerWidth} bind:clientHeight={containerHeight}>
|
||||||
@@ -346,6 +568,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="focus-field"
|
class="focus-field"
|
||||||
bind:this={domFocusField}
|
bind:this={domFocusField}
|
||||||
|
on:keydown={handleGridKeyDown}
|
||||||
on:focus={() => {
|
on:focus={() => {
|
||||||
currentDataGrid.set(instance);
|
currentDataGrid.set(instance);
|
||||||
}}
|
}}
|
||||||
@@ -436,6 +659,8 @@
|
|||||||
selectedCells={filterCellsForRow(selectedCells, rowIndex)}
|
selectedCells={filterCellsForRow(selectedCells, rowIndex)}
|
||||||
autofillMarkerCell={filterCellForRow(autofillMarkerCell, rowIndex)}
|
autofillMarkerCell={filterCellForRow(autofillMarkerCell, rowIndex)}
|
||||||
focusedColumn={display.focusedColumn}
|
focusedColumn={display.focusedColumn}
|
||||||
|
inplaceEditorState={$inplaceEditorState}
|
||||||
|
{dispatchInsplaceEditor}
|
||||||
{frameSelection}
|
{frameSelection}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DataGridCell from './DataGridCell.svelte';
|
import DataGridCell from './DataGridCell.svelte';
|
||||||
import { cellIsSelected } from './gridutil';
|
import { cellIsSelected } from './gridutil';
|
||||||
|
import InplaceEditor from './InplaceEditor.svelte';
|
||||||
|
|
||||||
import RowHeaderCell from './RowHeaderCell.svelte';
|
import RowHeaderCell from './RowHeaderCell.svelte';
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@
|
|||||||
export let autofillSelectedCells = undefined;
|
export let autofillSelectedCells = undefined;
|
||||||
export let autofillMarkerCell = undefined;
|
export let autofillMarkerCell = undefined;
|
||||||
export let focusedColumn = undefined;
|
export let focusedColumn = undefined;
|
||||||
|
export let inplaceEditorState;
|
||||||
|
export let dispatchInsplaceEditor;
|
||||||
|
|
||||||
$: rowData = grider.getRowData(rowIndex);
|
$: rowData = grider.getRowData(rowIndex);
|
||||||
$: rowStatus = grider.getRowStatus(rowIndex);
|
$: rowStatus = grider.getRowStatus(rowIndex);
|
||||||
@@ -29,16 +32,32 @@
|
|||||||
<tr style={`height: ${rowHeight}px`}>
|
<tr style={`height: ${rowHeight}px`}>
|
||||||
<RowHeaderCell {rowIndex} />
|
<RowHeaderCell {rowIndex} />
|
||||||
{#each visibleRealColumns as col (col.uniqueName)}
|
{#each visibleRealColumns as col (col.uniqueName)}
|
||||||
<DataGridCell
|
{#if inplaceEditorState.cell && rowIndex == inplaceEditorState.cell[0] && col.colIndex == inplaceEditorState.cell[1]}
|
||||||
{rowIndex}
|
<InplaceEditor
|
||||||
{rowData}
|
width={col.width}
|
||||||
{col}
|
{inplaceEditorState}
|
||||||
{hintFieldsAllowed}
|
{dispatchInsplaceEditor}
|
||||||
isSelected={frameSelection ? false : cellIsSelected(rowIndex, col.colIndex, selectedCells)}
|
cellValue={rowData[col.uniqueName]}
|
||||||
isFrameSelected={frameSelection ? cellIsSelected(rowIndex, col.colIndex, selectedCells) : false}
|
onSetValue={value => grider.setCellValue(rowIndex, col.uniqueName, value)}
|
||||||
isAutofillSelected={cellIsSelected(rowIndex, col.colIndex, autofillSelectedCells)}
|
/>
|
||||||
isFocusedColumn={col.uniqueName == focusedColumn}
|
{:else}
|
||||||
/>
|
<DataGridCell
|
||||||
|
{rowIndex}
|
||||||
|
{rowData}
|
||||||
|
{col}
|
||||||
|
{hintFieldsAllowed}
|
||||||
|
isSelected={frameSelection ? false : cellIsSelected(rowIndex, col.colIndex, selectedCells)}
|
||||||
|
isFrameSelected={frameSelection ? cellIsSelected(rowIndex, col.colIndex, selectedCells) : false}
|
||||||
|
isAutofillSelected={cellIsSelected(rowIndex, col.colIndex, autofillSelectedCells)}
|
||||||
|
isFocusedColumn={col.uniqueName == focusedColumn}
|
||||||
|
isModifiedCell={rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)}
|
||||||
|
isModifiedRow={rowStatus.status == 'updated'}
|
||||||
|
isInserted={rowStatus.status == 'inserted' ||
|
||||||
|
(rowStatus.insertedFields && rowStatus.insertedFields.has(col.uniqueName))}
|
||||||
|
isDeleted={rowStatus.status == 'deleted' ||
|
||||||
|
(rowStatus.deletedFields && rowStatus.deletedFields.has(col.uniqueName))}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|||||||
80
packages/web/src/datagrid/InplaceEditor.svelte
Normal file
80
packages/web/src/datagrid/InplaceEditor.svelte
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import keycodes from '../utility/keycodes';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
export let inplaceEditorState;
|
||||||
|
export let dispatchInsplaceEditor;
|
||||||
|
export let onSetValue;
|
||||||
|
export let width;
|
||||||
|
export let cellValue;
|
||||||
|
|
||||||
|
let domEditor;
|
||||||
|
|
||||||
|
const widthCopy = width;
|
||||||
|
|
||||||
|
const isChangedRef = { current: !!inplaceEditorState.text };
|
||||||
|
|
||||||
|
function handleKeyDown(event) {
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case keycodes.escape:
|
||||||
|
isChangedRef.current = false;
|
||||||
|
dispatchInsplaceEditor({ type: 'close' });
|
||||||
|
break;
|
||||||
|
case keycodes.enter:
|
||||||
|
if (isChangedRef.current) {
|
||||||
|
// grider.setCellValue(rowIndex, uniqueName, editor.value);
|
||||||
|
onSetValue(domEditor.value);
|
||||||
|
isChangedRef.current = false;
|
||||||
|
}
|
||||||
|
domEditor.blur();
|
||||||
|
dispatchInsplaceEditor({ type: 'close', mode: 'enter' });
|
||||||
|
break;
|
||||||
|
case keycodes.s:
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
if (isChangedRef.current) {
|
||||||
|
onSetValue(domEditor.value);
|
||||||
|
// grider.setCellValue(rowIndex, uniqueName, editor.value);
|
||||||
|
isChangedRef.current = false;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
dispatchInsplaceEditor({ type: 'close', mode: 'save' });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlur() {
|
||||||
|
if (isChangedRef.current) {
|
||||||
|
onSetValue(domEditor.value);
|
||||||
|
// grider.setCellValue(rowIndex, uniqueName, editor.value);
|
||||||
|
isChangedRef.current = false;
|
||||||
|
}
|
||||||
|
dispatchInsplaceEditor({ type: 'close' });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
domEditor.value = inplaceEditorState.text || cellValue;
|
||||||
|
domEditor.focus();
|
||||||
|
if (inplaceEditorState.selectAll) {
|
||||||
|
domEditor.select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
on:change={() => (isChangedRef.current = true)}
|
||||||
|
on:keydown={handleKeyDown}
|
||||||
|
on:blur={handleBlur}
|
||||||
|
bind:this={domEditor}
|
||||||
|
style={`width:${widthCopy}px;min-width:${widthCopy}px;max-width:${widthCopy}px`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input {
|
||||||
|
border: 0px solid;
|
||||||
|
outline: none;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -44,6 +44,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { changeSetToSql, createChangeSet } from 'dbgate-datalib';
|
||||||
|
import { scriptToSql } from 'dbgate-sqltree';
|
||||||
|
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
||||||
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
|
||||||
import axios from '../utility/axios';
|
import axios from '../utility/axios';
|
||||||
import ChangeSetGrider from './ChangeSetGrider';
|
import ChangeSetGrider from './ChangeSetGrider';
|
||||||
|
|
||||||
@@ -55,11 +61,47 @@
|
|||||||
export let schemaName;
|
export let schemaName;
|
||||||
export let pureName;
|
export let pureName;
|
||||||
export let config;
|
export let config;
|
||||||
|
export let changeSetState;
|
||||||
|
export let dispatchChangeSet;
|
||||||
|
|
||||||
let loadedRows = [];
|
let loadedRows = [];
|
||||||
|
|
||||||
// $: console.log('loadedRows BIND', loadedRows);
|
// $: console.log('loadedRows BIND', loadedRows);
|
||||||
$: grider = new ChangeSetGrider(loadedRows, null, null, display);
|
$: grider = new ChangeSetGrider(loadedRows, changeSetState, dispatchChangeSet, display);
|
||||||
// $: console.log('GRIDER', grider);
|
// $: console.log('GRIDER', grider);
|
||||||
|
|
||||||
|
async function handleConfirmSql(sql) {
|
||||||
|
const resp = await axios.request({
|
||||||
|
url: 'database-connections/query-data',
|
||||||
|
method: 'post',
|
||||||
|
params: {
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
},
|
||||||
|
data: { sql },
|
||||||
|
});
|
||||||
|
const { errorMessage } = resp.data || {};
|
||||||
|
if (errorMessage) {
|
||||||
|
showModal(ErrorMessageModal, { title: 'Error when saving', message: errorMessage });
|
||||||
|
} else {
|
||||||
|
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
|
||||||
|
display.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
const script = changeSetToSql(changeSetState && changeSetState.value, display.dbinfo);
|
||||||
|
const sql = scriptToSql(display.driver, script);
|
||||||
|
showModal(ConfirmSqlModal, { sql, onConfirm: () => handleConfirmSql(sql) });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LoadingDataGridCore {...$$props} {loadDataPage} {dataPageAvailable} {loadRowCount} bind:loadedRows {grider} />
|
<LoadingDataGridCore
|
||||||
|
{...$$props}
|
||||||
|
{loadDataPage}
|
||||||
|
{dataPageAvailable}
|
||||||
|
{loadRowCount}
|
||||||
|
bind:loadedRows
|
||||||
|
{grider}
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
|||||||
38
packages/web/src/modals/ConfirmSqlModal.svelte
Normal file
38
packages/web/src/modals/ConfirmSqlModal.svelte
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script>
|
||||||
|
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||||
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
|
||||||
|
import ModalBase from './ModalBase.svelte';
|
||||||
|
import { closeCurrentModal } from './modalTools';
|
||||||
|
|
||||||
|
export let sql;
|
||||||
|
export let onConfirm;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormProvider>
|
||||||
|
<ModalBase {...$$restProps}>
|
||||||
|
<div slot="header">Save changes</div>
|
||||||
|
|
||||||
|
<textarea class="editor" value={sql} />
|
||||||
|
|
||||||
|
<div slot="footer">
|
||||||
|
<FormSubmit
|
||||||
|
value="OK"
|
||||||
|
on:click={() => {
|
||||||
|
closeCurrentModal();
|
||||||
|
onConfirm();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormStyledButton type="button" value="Close" onClick={closeCurrentModal} />
|
||||||
|
</div>
|
||||||
|
</ModalBase>
|
||||||
|
</FormProvider>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.editor {
|
||||||
|
position: relative;
|
||||||
|
height: 30vh;
|
||||||
|
width: 40vw;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
41
packages/web/src/modals/ErrorMessageModal.svelte
Normal file
41
packages/web/src/modals/ErrorMessageModal.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||||
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
|
||||||
|
import ModalBase from './ModalBase.svelte';
|
||||||
|
import { closeCurrentModal } from './modalTools';
|
||||||
|
|
||||||
|
export let title = 'Error';
|
||||||
|
export let message;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FormProvider>
|
||||||
|
<ModalBase {...$$restProps}>
|
||||||
|
<div slot="header">{title}</div>
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="icon">
|
||||||
|
<FontIcon icon="img error" />
|
||||||
|
</div>
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="footer">
|
||||||
|
<FormStyledButton type="button" value="Close" onClick={closeCurrentModal} />
|
||||||
|
</div>
|
||||||
|
</ModalBase>
|
||||||
|
</FormProvider>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { writable, derived } from 'svelte/store';
|
import { writable, derived, readable } from 'svelte/store';
|
||||||
import { ExtensionsDirectory } from 'dbgate-types';
|
import { ExtensionsDirectory } from 'dbgate-types';
|
||||||
|
|
||||||
interface TabDefinition {
|
interface TabDefinition {
|
||||||
@@ -37,6 +37,7 @@ export const visibleToolbar = writableWithStorage(1, 'visibleToolbar');
|
|||||||
export const leftPanelWidth = writable(300);
|
export const leftPanelWidth = writable(300);
|
||||||
export const currentDropDownMenu = writable(null);
|
export const currentDropDownMenu = writable(null);
|
||||||
export const openedModals = writable([]);
|
export const openedModals = writable([]);
|
||||||
|
export const nullStore = readable(null, () => {});
|
||||||
|
|
||||||
subscribeCssVariable(selectedWidget, x => (x ? 1 : 0), '--dim-visible-left-panel');
|
subscribeCssVariable(selectedWidget, x => (x ? 1 : 0), '--dim-visible-left-panel');
|
||||||
subscribeCssVariable(visibleToolbar, x => (x ? 1 : 0), '--dim-visible-toolbar');
|
subscribeCssVariable(visibleToolbar, x => (x ? 1 : 0), '--dim-visible-toolbar');
|
||||||
|
|||||||
@@ -2,9 +2,16 @@
|
|||||||
import App from '../App.svelte';
|
import App from '../App.svelte';
|
||||||
import TableDataGrid from '../datagrid/TableDataGrid.svelte';
|
import TableDataGrid from '../datagrid/TableDataGrid.svelte';
|
||||||
import useGridConfig from '../utility/useGridConfig';
|
import useGridConfig from '../utility/useGridConfig';
|
||||||
import { createGridCache, createGridConfig, TableFormViewDisplay, TableGridDisplay } from 'dbgate-datalib';
|
import {
|
||||||
|
createChangeSet,
|
||||||
|
createGridCache,
|
||||||
|
createGridConfig,
|
||||||
|
TableFormViewDisplay,
|
||||||
|
TableGridDisplay,
|
||||||
|
} from 'dbgate-datalib';
|
||||||
import { findEngineDriver } from 'dbgate-tools';
|
import { findEngineDriver } from 'dbgate-tools';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
import createUndoReducer from '../utility/createUndoReducer';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -14,6 +21,17 @@
|
|||||||
|
|
||||||
const config = useGridConfig(tabid);
|
const config = useGridConfig(tabid);
|
||||||
const cache = writable(createGridCache());
|
const cache = writable(createGridCache());
|
||||||
|
|
||||||
|
const [changeSetStore, dispatchChangeSet] = createUndoReducer(createChangeSet());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TableDataGrid {...$$props} config={$config} setConfig={config.update} cache={$cache} setCache={cache.update} />
|
<TableDataGrid
|
||||||
|
{...$$props}
|
||||||
|
config={$config}
|
||||||
|
setConfig={config.update}
|
||||||
|
cache={$cache}
|
||||||
|
setCache={cache.update}
|
||||||
|
changeSetState={$changeSetStore}
|
||||||
|
{changeSetStore}
|
||||||
|
{dispatchChangeSet}
|
||||||
|
/>
|
||||||
|
|||||||
11
packages/web/src/utility/createReducer.ts
Normal file
11
packages/web/src/utility/createReducer.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export default function createReducer(reducer, initialState): any {
|
||||||
|
const state = writable(initialState);
|
||||||
|
|
||||||
|
function dispatch(action) {
|
||||||
|
state.update(x => reducer(x, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [state, dispatch];
|
||||||
|
}
|
||||||
69
packages/web/src/utility/createUndoReducer.ts
Normal file
69
packages/web/src/utility/createUndoReducer.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import createReducer from './createReducer';
|
||||||
|
|
||||||
|
const reducer = options => (state, action) => {
|
||||||
|
const { mergeNearActions } = options || {};
|
||||||
|
|
||||||
|
const useMerge =
|
||||||
|
action.useMerge || (mergeNearActions && state.lastActionTm && new Date().getTime() - state.lastActionTm < 100);
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case 'set':
|
||||||
|
return {
|
||||||
|
history: [...state.history.slice(0, useMerge ? state.current : state.current + 1), action.value],
|
||||||
|
current: useMerge ? state.current : state.current + 1,
|
||||||
|
value: action.value,
|
||||||
|
canUndo: true,
|
||||||
|
canRedo: false,
|
||||||
|
lastActionTm: new Date().getTime(),
|
||||||
|
};
|
||||||
|
case 'compute': {
|
||||||
|
const newValue = action.compute(state.history[state.current]);
|
||||||
|
return {
|
||||||
|
history: [...state.history.slice(0, useMerge ? state.current : state.current + 1), newValue],
|
||||||
|
current: useMerge ? state.current : state.current + 1,
|
||||||
|
value: newValue,
|
||||||
|
canUndo: true,
|
||||||
|
canRedo: false,
|
||||||
|
lastActionTm: new Date().getTime(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'undo':
|
||||||
|
if (state.current > 0)
|
||||||
|
return {
|
||||||
|
history: state.history,
|
||||||
|
current: state.current - 1,
|
||||||
|
value: state.history[state.current - 1],
|
||||||
|
canUndo: state.current > 1,
|
||||||
|
canRedo: true,
|
||||||
|
lastActionTm: null,
|
||||||
|
};
|
||||||
|
return state;
|
||||||
|
case 'redo':
|
||||||
|
if (state.current < state.history.length - 1)
|
||||||
|
return {
|
||||||
|
history: state.history,
|
||||||
|
current: state.current + 1,
|
||||||
|
value: state.history[state.current + 1],
|
||||||
|
canUndo: true,
|
||||||
|
canRedo: state.current < state.history.length - 2,
|
||||||
|
lastActionTm: null,
|
||||||
|
};
|
||||||
|
return state;
|
||||||
|
case 'reset':
|
||||||
|
return {
|
||||||
|
history: [action.value],
|
||||||
|
current: 0,
|
||||||
|
value: action.value,
|
||||||
|
lastActionTm: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function createUndoReducer(initialValue, options = null) {
|
||||||
|
return createReducer(reducer(options), {
|
||||||
|
history: [initialValue],
|
||||||
|
current: 0,
|
||||||
|
value: initialValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user