diff --git a/packages/engines/default/SqlDumper.js b/packages/engines/default/SqlDumper.js index ca053967..0a2cf41e 100644 --- a/packages/engines/default/SqlDumper.js +++ b/packages/engines/default/SqlDumper.js @@ -33,9 +33,12 @@ class SqlDumper { this.putRaw("'"); } putValue(value) { - if (_.isString(value)) this.putStringValue(value); - if (_.isNumber(value)) this.putRaw(value.toString()); - if (_.isDate(value)) this.putStringValue(moment(value).toISOString()); + if (value === null) this.putRaw('NULL'); + if (value === true) this.putRaw('1'); + if (value === false) this.putRaw('0'); + else if (_.isString(value)) this.putStringValue(value); + else if (_.isNumber(value)) this.putRaw(value.toString()); + else if (_.isDate(value)) this.putStringValue(moment(value).toISOString()); } putCmd(format, ...args) { this.put(format, ...args); @@ -77,7 +80,7 @@ class SqlDumper { } putFormattedList(c, collection) { if (!collection) return; - this.putCollection(', ', collection, item => this.putFormattedValue(c, item)); + this.putCollection(', ', collection, (item) => this.putFormattedValue(c, item)); } /** @param format {string} */ put(format, ...args) { @@ -198,7 +201,7 @@ class SqlDumper { /** @param table {import('@dbgate/types').TableInfo} */ createTable(table) { this.put('^create ^table %f ( &>&n', table); - this.putCollection(',&n', table.columns, col => { + this.putCollection(',&n', table.columns, (col) => { this.put('%i ', col.columnName); this.columnDefinition(col); }); @@ -209,11 +212,11 @@ class SqlDumper { } this.put( ' ^primary ^key (%,i)', - table.primaryKey.columns.map(x => x.columnName) + table.primaryKey.columns.map((x) => x.columnName) ); } if (table.foreignKeys) { - table.foreignKeys.forEach(fk => { + table.foreignKeys.forEach((fk) => { this.put(',&n'); this.createForeignKeyFore(fk); }); @@ -243,9 +246,9 @@ class SqlDumper { if (fk.constraintName != null) this.put('^constraint %i ', fk.constraintName); this.put( '^foreign ^key (%,i) ^references %f (%,i)', - fk.columns.map(x => x.columnName), + fk.columns.map((x) => x.columnName), { schemaName: fk.refSchemaName, pureName: fk.refTableName }, - fk.columns.map(x => x.refColumnName) + fk.columns.map((x) => x.refColumnName) ); if (fk.deleteAction) this.put(' ^on ^delete %k', fk.deleteAction); if (fk.updateAction) this.put(' ^on ^update %k', fk.updateAction); diff --git a/packages/web/src/datagrid/DataGridContextMenu.js b/packages/web/src/datagrid/DataGridContextMenu.js index 9939e276..7ff2b39d 100644 --- a/packages/web/src/datagrid/DataGridContextMenu.js +++ b/packages/web/src/datagrid/DataGridContextMenu.js @@ -1,7 +1,14 @@ import React from 'react'; import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu'; -export default function DataGridContextMenu({ copy, revertRowChanges, deleteSelectedRows, insertNewRow, reload }) { +export default function DataGridContextMenu({ + copy, + revertRowChanges, + deleteSelectedRows, + insertNewRow, + setNull, + reload, +}) { return ( <> @@ -20,6 +27,10 @@ export default function DataGridContextMenu({ copy, revertRowChanges, deleteSele Insert new row + + + Set NULL + ); } diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index a555dffb..9915729e 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -530,6 +530,7 @@ export default function DataGridCore(props) { deleteSelectedRows={deleteSelectedRows} insertNewRow={insertNewRow} reload={() => display.reload()} + setNull={setNull} /> ); }; @@ -654,6 +655,22 @@ export default function DataGridCore(props) { setChangeSet(chs); } + function setNull() { + let chs = changeSet; + selectedCells.filter(isRegularCell).forEach((cell) => { + chs = setChangeSetValue( + chs, + display.getChangeSetField( + loadedAndInsertedRows[cell[0]], + realColumnUniqueNames[cell[1]], + cell[0] >= loadedRows.length ? cell[0] - loadedRows.length : null + ), + null + ); + }); + setChangeSet(chs); + } + function copyToClipboard() { const rowIndexes = _.uniq(selectedCells.map((x) => x[0])).sort(); const lines = rowIndexes.map((rowIndex) => { @@ -844,6 +861,11 @@ export default function DataGridCore(props) { // this.saveAndFocus(); } + if (event.keyCode == keycodes.n0 && event.ctrlKey) { + event.preventDefault(); + setNull(); + } + if (event.keyCode == keycodes.r && event.ctrlKey) { event.preventDefault(); revertRowChanges(); diff --git a/packages/web/src/datagrid/DataGridRow.js b/packages/web/src/datagrid/DataGridRow.js index d984abd9..caa2d79d 100644 --- a/packages/web/src/datagrid/DataGridRow.js +++ b/packages/web/src/datagrid/DataGridRow.js @@ -68,8 +68,7 @@ const TableBodyCell = styled.td` `} ${(props) => - props.isFocusedColumn - && + props.isFocusedColumn && ` background-color: lightgoldenrodyellow; `} @@ -123,11 +122,31 @@ const AutoFillPoint = styled.div` cursor: crosshair; `; +function makeBulletString(value) { + return _.pad('', value.length, '•'); +} + +function highlightSpecialCharacters(value) { + value = value.replace(/\n/g, '↲'); + value = value.replace(/\r/g, ''); + value = value.replace(/^(\s+)/, makeBulletString); + value = value.replace(/(\s+)$/, makeBulletString); + value = value.replace(/(\s\s+)/g, makeBulletString); + return value; +} + +const dateTimeRegex = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?/; + function CellFormattedValue({ value }) { if (value == null) return (NULL); if (_.isDate(value)) return moment(value).format('YYYY-MM-DD HH:mm:ss'); if (value === true) return '1'; if (value === false) return '0'; + if (_.isNumber(value)) return value.toLocaleString(); + if (_.isString(value)) { + if (dateTimeRegex.test(value)) return moment(value).format('YYYY-MM-DD HH:mm:ss'); + return highlightSpecialCharacters(value); + } return value; } diff --git a/packages/web/src/widgets/Splitter.js b/packages/web/src/widgets/Splitter.js index 06de1d25..cbcf5ef9 100644 --- a/packages/web/src/widgets/Splitter.js +++ b/packages/web/src/widgets/Splitter.js @@ -29,14 +29,18 @@ export const VerticalSplitHandle = styled.div` background-color: #ccc; height: ${theme.splitter.thickness}px; cursor: row-resize; - z-index: 1; + &:hover { + background-color: #AAA; + } `; export const HorizontalSplitHandle = styled.div` background-color: #ccc; width: ${theme.splitter.thickness}px; cursor: col-resize; - z-index: 1; + &:hover { + background-color: #AAA; + } `; const ChildContainer1 = styled.div` @@ -45,6 +49,7 @@ const ChildContainer1 = styled.div` // flex-grow: 1; display: flex; position: relative; + overflow: hidden; `; const ChildContainer2 = styled.div` @@ -54,6 +59,7 @@ const ChildContainer2 = styled.div` // flex-grow: 1; display: flex; position: relative; + overflow: hidden; `; export function useSplitterDrag(axes, onResize) { diff --git a/packages/web/src/widgets/ToolbarButton.js b/packages/web/src/widgets/ToolbarButton.js index c331b9ad..0eaa6f07 100644 --- a/packages/web/src/widgets/ToolbarButton.js +++ b/packages/web/src/widgets/ToolbarButton.js @@ -38,6 +38,7 @@ const StyledIconSpan = styled.span` const ButtonDivInner = styled.div` position: relative; top: ${(props) => props.patchY}px; + white-space: nowrap; `; export default function ToolbarButton({ children, onClick, icon = undefined, disabled = undefined, patchY = 2 }) {