From 8778d5ff6426500382f9a6c78f37d7018db53e42 Mon Sep 17 00:00:00 2001 From: Joao Pereira and Sarah McAlear Date: Mon, 8 May 2017 17:28:00 -0400 Subject: [PATCH 1/4] Improves user's ability to select cells in query results - user can select columns - user can modify column or row selection with shift+arrow - user can select entire grid with ctrl+A or cmd+A - user can copy from grid using keyboard shortcuts --- libraries.txt | 2 +- .../tables/tests/test_column_properties_sql.py | 2 +- .../copy_selected_query_results_feature_test.py | 71 +- .../static/js/selection/active_cell_capture.js | 152 + web/pgadmin/static/js/selection/column_selector.js | 45 +- web/pgadmin/static/js/selection/copy_data.js | 13 +- web/pgadmin/static/js/selection/grid_selector.js | 39 +- .../js/selection/range_boundary_navigator.js | 7 +- .../static/js/selection/range_selection_helper.js | 12 +- web/pgadmin/static/js/selection/row_selector.js | 5 +- web/pgadmin/static/js/slickgrid/cell_selector.js | 18 + .../handle_query_output_keyboard_event.js | 21 + web/pgadmin/static/vendor/slickgrid/README | 9 - web/pgadmin/static/vendor/slickgrid/README.md | 9 + .../slickgrid/controls/slick.columnpicker.js | 9 +- .../static/vendor/slickgrid/images/CheckboxN.png | Bin 0 -> 257 bytes .../static/vendor/slickgrid/images/CheckboxY.png | Bin 0 -> 361 bytes .../vendor/slickgrid/plugins/slick.autotooltips.js | 10 +- .../slickgrid/plugins/slick.cellcopymanager.js | 6 +- .../plugins/slick.cellexternalcopymanager.js | 449 ++ .../slickgrid/plugins/slick.cellrangeselector.js | 9 +- .../slickgrid/plugins/slick.cellselectionmodel.js | 46 +- .../slickgrid/plugins/slick.headerbuttons.js | 6 +- .../vendor/slickgrid/plugins/slick.headermenu.js | 8 +- .../slickgrid/plugins/slick.rowselectionmodel.js | 8 +- .../vendor/slickgrid/slick-default-theme.css | 22 +- web/pgadmin/static/vendor/slickgrid/slick.core.js | 15 +- .../static/vendor/slickgrid/slick.dataview.js | 40 +- .../static/vendor/slickgrid/slick.editors.js | 67 +- .../static/vendor/slickgrid/slick.formatters.js | 12 +- web/pgadmin/static/vendor/slickgrid/slick.grid.css | 43 +- web/pgadmin/static/vendor/slickgrid/slick.grid.js | 7400 ++++++++++---------- .../slickgrid/slick.groupitemmetadataprovider.js | 158 + .../vendor/slickgrid/slick.remotemodel-yahoo.js | 206 + .../static/vendor/slickgrid/slick.remotemodel.js | 169 + .../tools/sqleditor/static/css/sqleditor.css | 34 +- .../sqleditor/templates/sqleditor/js/sqleditor.js | 76 +- .../utils/tests/test_versioned_template_loader.py | 25 +- .../selection/active_cell_capture_spec.js | 231 + .../javascript/selection/column_selector_spec.js | 291 +- .../javascript/selection/copy_data_spec.js | 9 + .../javascript/selection/grid_selector_spec.js | 4 +- .../selection/range_boundary_navigator_spec.js | 19 +- .../javascript/selection/row_selector_spec.js | 75 +- .../javascript/slickgrid/cell_selector_spec.js | 77 + .../handle_query_output_keyboard_event_spec.js | 145 + web/regression/javascript/test-main.js | 3 + web/regression/python_test_utils/test_utils.py | 8 +- 48 files changed, 6087 insertions(+), 3998 deletions(-) create mode 100644 web/pgadmin/static/js/selection/active_cell_capture.js create mode 100644 web/pgadmin/static/js/slickgrid/cell_selector.js create mode 100644 web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js delete mode 100644 web/pgadmin/static/vendor/slickgrid/README create mode 100644 web/pgadmin/static/vendor/slickgrid/README.md create mode 100644 web/pgadmin/static/vendor/slickgrid/images/CheckboxN.png create mode 100644 web/pgadmin/static/vendor/slickgrid/images/CheckboxY.png create mode 100644 web/pgadmin/static/vendor/slickgrid/plugins/slick.cellexternalcopymanager.js create mode 100644 web/pgadmin/static/vendor/slickgrid/slick.groupitemmetadataprovider.js create mode 100644 web/pgadmin/static/vendor/slickgrid/slick.remotemodel-yahoo.js create mode 100644 web/pgadmin/static/vendor/slickgrid/slick.remotemodel.js create mode 100644 web/regression/javascript/selection/active_cell_capture_spec.js create mode 100644 web/regression/javascript/slickgrid/cell_selector_spec.js create mode 100644 web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js diff --git a/libraries.txt b/libraries.txt index 7e0bd5b7..a8c634c8 100644 --- a/libraries.txt +++ b/libraries.txt @@ -31,6 +31,6 @@ dropzone 4e20bd4 MIT https://github.com/enyo/dropzone Filemanager 7e060c2 MIT https://github.com/simogeo/Filemanager Unit (Length) d8e6237 MIT https://github.com/heygrady/Units/blob/master/Length.min.js Natural Sort 9565816 MIT https://github.com/javve/natural-sort/blob/master/index.js -SlickGrid 2.2.4 MIT https://github.com/6pac/SlickGrid +SlickGrid 2.3.6-53ee34 MIT https://github.com/6pac/SlickGrid jQuery-UI 1.11.3 MIT https://jqueryui.com/ BigNumber 3.0.1 MIT http://mikemcl.github.io/bignumber.js \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py index ce6fa972..47c7221f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py @@ -45,7 +45,7 @@ class TestColumnPropertiesSql(SQLTemplateTestBase): self.assertEqual('some_column', first_row['name']) self.assertEqual('character varying', first_row['cltype']) - self.assertEqual(2, len(fetch_result)) + self.assertEqual(3, len(fetch_result)) @staticmethod def get_template_file(version, filename): diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py index 338e5f71..08fc5556 100644 --- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py +++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py @@ -11,6 +11,7 @@ import pyperclip import time from selenium.webdriver import ActionChains +from selenium.webdriver.common.keys import Keys from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest @@ -21,9 +22,8 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): Tests various ways to copy data from the query results grid. """ - scenarios = [ - ("Test Copying Query Results", dict()) + ("Copy rows, column using button and keyboard shortcut", dict()) ] def before(self): @@ -50,27 +50,82 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe")) self.page.find_by_id("btn-flash").click() + time.sleep(5) self._copies_rows() self._copies_columns() + self._copies_row_using_keyboard_shortcut() + self._copies_column_using_keyboard_shortcut() + self._shift_resizes_rectangular_selection() + self._shift_resizes_column_selection() def _copies_rows(self): pyperclip.copy("old clipboard contents") - time.sleep(5) - self.page.find_by_xpath("//*[contains(@class, 'sr')]/*[1]/input[@type='checkbox']").click() + self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]").click() + self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.assertEqual("'Some-Name','6'", + self.assertEqual("'Some-Name','6','some info'", pyperclip.paste()) def _copies_columns(self): pyperclip.copy("old clipboard contents") - - self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]/input").click() + self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click() self.page.find_by_xpath("//*[@id='btn-copy-row']").click() self.assertEqual( """'Some-Name' -'Some-Other-Name'""", +'Some-Other-Name' +'Yet-Another-Name'""", + pyperclip.paste()) + + def _copies_row_using_keyboard_shortcut(self): + pyperclip.copy("old clipboard contents") + self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]/input[@type='checkbox']").click() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual("'Some-Name','6','some info'", + pyperclip.paste()) + + def _copies_column_using_keyboard_shortcut(self): + pyperclip.copy("old clipboard contents") + self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual( + """'Some-Name' +'Some-Other-Name' +'Yet-Another-Name'""", + pyperclip.paste()) + + def _shift_resizes_rectangular_selection(self): + pyperclip.copy("old clipboard contents") + + top_left_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") + initial_bottom_right_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]") + ActionChains(self.page.driver).click_and_hold(top_left_cell).move_to_element(initial_bottom_right_cell)\ + .release(initial_bottom_right_cell).perform() + + ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_RIGHT).key_up(Keys.SHIFT).perform() + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual("""'Some-Other-Name','22','some other info' +'Yet-Another-Name','14','cool info'""", pyperclip.paste()) + + def _shift_resizes_column_selection(self): + pyperclip.copy("old clipboard contents") + + self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'value')]").click() + ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_LEFT)\ + .key_up(Keys.SHIFT).perform() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual( + """'Some-Name','6' +'Some-Other-Name','22' +'Yet-Another-Name','14'""", pyperclip.paste()) def after(self): diff --git a/web/pgadmin/static/js/selection/active_cell_capture.js b/web/pgadmin/static/js/selection/active_cell_capture.js new file mode 100644 index 00000000..fe8cbd81 --- /dev/null +++ b/web/pgadmin/static/js/selection/active_cell_capture.js @@ -0,0 +1,152 @@ +define([ + 'jquery', + 'sources/selection/range_selection_helper' +], function ($, rangeSelectionHelper) { + + var ActiveCellCapture = function () { + var KEY_RIGHT = 39; + var KEY_LEFT = 37; + var KEY_UP = 38; + var KEY_DOWN = 40; + + var bypassDefaultActiveCellRangeChange = false; + var grid; + + var init = function (slickGrid) { + grid = slickGrid; + grid.onDragEnd.subscribe(onDragEndHandler); + grid.onHeaderClick.subscribe(onHeaderClickHandler); + grid.onClick.subscribe(onClickHandler); + grid.onActiveCellChanged.subscribe(onActiveCellChangedHandler); + grid.onKeyDown.subscribe(onKeyDownHandler); + }; + + var destroy = function () { + grid.onDragEnd.unsubscribe(onDragEndHandler); + grid.onHeaderClick.unsubscribe(onHeaderClickHandler); + grid.onActiveCellChanged.unsubscribe(onActiveCellChangedHandler); + grid.onKeyDown.unsubscribe(onKeyDownHandler); + }; + + $.extend(this, { + "init": init, + "destroy": destroy, + }); + + function onDragEndHandler(event, dragData) { + bypassDefaultActiveCellRangeChange = true; + grid.setActiveCell(dragData.range.start.row, dragData.range.start.cell); + } + + function onHeaderClickHandler(event, args) { + bypassDefaultActiveCellRangeChange = true; + + var clickedColumn = args.column.pos + 1; + if (isClickingLastClickedHeader(0, clickedColumn)) { + if (isSingleRangeSelected()) { + grid.resetActiveCell(); + } else { + grid.setActiveCell(0, retrievePreviousSelectedRange().fromCell); + } + } else if (!isClickingInSelectedColumn(clickedColumn)) { + grid.setActiveCell(0, clickedColumn); + } + } + + function onClickHandler(event, args) { + if (isRowHeader(args.cell)) { + bypassDefaultActiveCellRangeChange = true; + var rowClicked = args.row; + + if (isClickingLastClickedHeader(rowClicked, 1)) { + if (isSingleRangeSelected()) { + grid.resetActiveCell(); + } else { + grid.setActiveCell(retrievePreviousSelectedRange().fromRow, 1); + } + } else if (!isClickingInSelectedRow(rowClicked)) { + grid.setActiveCell(rowClicked, 1); + } + } + } + + function onActiveCellChangedHandler(event, args) { + if (bypassDefaultActiveCellRangeChange) { + bypassDefaultActiveCellRangeChange = false; + event.stopPropagation(); + } + } + + function onKeyDownHandler(event) { + if (hasActiveCell() && isShiftArrowKey(event)) { + selectOnlyRangeOfActiveCell(); + } + } + + function isClickingLastClickedHeader(clickedRow, clickedColumn) { + return hasActiveCell() && grid.getActiveCell().row === clickedRow && grid.getActiveCell().cell === clickedColumn; + } + + function isClickingInSelectedColumn(clickedColumn) { + var column = rangeSelectionHelper.rangeForColumn(grid, clickedColumn); + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return rangeSelectionHelper.isRangeSelected(ranges, column); + } + + function isRowHeader(cellClicked) { + return grid.getColumns()[cellClicked].id === 'row-header-column'; + } + + function isClickingInSelectedRow(rowClicked) { + var row = rangeSelectionHelper.rangeForRow(grid, rowClicked); + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return rangeSelectionHelper.isRangeSelected(ranges, row); + } + + function isSingleRangeSelected() { + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return ranges.length === 1; + } + + function retrievePreviousSelectedRange() { + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + return ranges[ranges.length - 2]; + } + + function isArrowKey(event) { + return event.which === KEY_RIGHT + || event.which === KEY_UP + || event.which === KEY_LEFT + || event.which === KEY_DOWN; + } + + function isModifiedByShiftOnly(event) { + return event.shiftKey + && !event.ctrlKey + && !event.altKey; + } + + function isShiftArrowKey(event) { + return isModifiedByShiftOnly(event) && isArrowKey(event); + } + + function hasActiveCell() { + return !!grid.getActiveCell(); + } + + function selectOnlyRangeOfActiveCell() { + var cellSelectionModel = grid.getSelectionModel(); + var ranges = cellSelectionModel.getSelectedRanges(); + + if (ranges.length > 1) { + cellSelectionModel.setSelectedRanges([ranges.pop()]); + } + } + }; + + return ActiveCellCapture; +}); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js index c89b3fa8..2923a796 100644 --- a/web/pgadmin/static/js/selection/column_selector.js +++ b/web/pgadmin/static/js/selection/column_selector.js @@ -1,20 +1,29 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) { var ColumnSelector = function () { + var Slick = window.Slick; + var gridEventBus = new Slick.EventHandler(); + var init = function (grid) { - grid.onHeaderClick.subscribe(function (event, eventArgument) { - var column = eventArgument.column; + gridEventBus.subscribe(grid.onHeaderClick, function (event, eventArgument) { + var columnDefinition = eventArgument.column; - if (column.selectable !== false) { + grid.focus(); - if (!clickedCheckbox(event)) { - var $checkbox = $("[data-id='checkbox-" + column.id + "']"); - toggleCheckbox($checkbox); - } + if (isColumnSelectable(columnDefinition)) { + var $columnHeader = $(event.target); + if(hasClickedChildOfColumnHeader(event)) { + $columnHeader = $(event.target).parents(".slick-header-column"); + } + $columnHeader.toggleClass('selected'); - updateRanges(grid, column.id); + if (!hasClickedOnCheckbox(event)) { + var $checkbox = $("[data-id='checkbox-" + columnDefinition.id + "']"); + toggleCheckbox($checkbox); } + + updateRanges(grid, columnDefinition.id); } - ); + }); grid.getSelectionModel().onSelectedRangesChanged .subscribe(handleSelectedRangesChanged.bind(null, grid)); }; @@ -24,7 +33,8 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func .each(function (index, checkbox) { var $checkbox = $(checkbox); var columnIndex = grid.getColumnIndex($checkbox.data('column-id')); - var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, rangeSelectionHelper.rangeForColumn(grid, columnIndex)); + var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, + rangeSelectionHelper.rangeForColumn(grid, columnIndex)); if (!isStillSelected) { toggleCheckbox($checkbox); } @@ -51,8 +61,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func selectionModel.setSelectedRanges(newRanges); }; - var clickedCheckbox = function (e) { - return e.target.type == "checkbox" + var hasClickedOnCheckbox = function (event) { + return event.target.type == "checkbox" + }; + + + var hasClickedChildOfColumnHeader = function (event) { + return !$(event.target).hasClass("slick-header-column"); }; var toggleCheckbox = function (checkbox) { @@ -63,9 +78,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func } }; + var isColumnSelectable = function (columnDefinition) { + return columnDefinition.selectable !== false; + }; + var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) { return _.map(columnDefinitions, function (columnDefinition) { - if (columnDefinition.selectable !== false) { + if (isColumnSelectable(columnDefinition)) { var name = "" + diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js index 018efead..166219ba 100644 --- a/web/pgadmin/static/js/selection/copy_data.js +++ b/web/pgadmin/static/js/selection/copy_data.js @@ -3,8 +3,9 @@ define([ 'underscore', 'sources/selection/clipboard', 'sources/selection/range_selection_helper', - 'sources/selection/range_boundary_navigator'], - function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) { + 'sources/selection/range_boundary_navigator' + ], +function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) { var copyData = function () { var self = this; @@ -14,7 +15,6 @@ define([ var data = grid.getData(); var rows = grid.getSelectedRows(); - if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) { self.copied_rows = rows.map(function (rowIndex) { return data[rowIndex]; @@ -24,7 +24,6 @@ define([ self.copied_rows = []; setPasteRowButtonEnablement(self.can_edit, false); } - var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges); if (csvText) { clipboard.copyTextToClipboard(csvText); @@ -45,8 +44,8 @@ define([ if(RangeSelectionHelper.isFirstColumnData(columnDefinitions)) { return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]); } - return _.isEqual(_.union.apply(null, colRangeBounds), [1, columnDefinitions.length - 1]); + return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]); }; - return copyData; -}); + return copyData +}); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/grid_selector.js b/web/pgadmin/static/js/selection/grid_selector.js index 31aee69f..90eeacf8 100644 --- a/web/pgadmin/static/js/selection/grid_selector.js +++ b/web/pgadmin/static/js/selection/grid_selector.js @@ -1,5 +1,8 @@ -define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_selector'], - function ($, ColumnSelector, RowSelector) { +define(['jquery', + 'sources/selection/column_selector', + 'sources/selection/row_selector', + 'sources/selection/range_selection_helper'], + function ($, ColumnSelector, RowSelector, RangeSelectionHelper) { var Slick = window.Slick; var GridSelector = function (columnDefinitions) { @@ -31,44 +34,22 @@ define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_se }; function handleSelectedRangesChanged(grid) { - $("[data-id='checkbox-select-all']").prop("checked", isEntireGridSelected(grid)); - } - - function isEntireGridSelected(grid) { - var selectionModel = grid.getSelectionModel(); - var selectedRanges = selectionModel.getSelectedRanges(); - return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid)); + $("[data-id='checkbox-select-all']").prop("checked", RangeSelectionHelper.isEntireGridSelected(grid)); } function toggleSelectAll(grid) { - if (isEntireGridSelected(grid)) { - deselect(grid); + if (RangeSelectionHelper.isEntireGridSelected(grid)) { + selectNone(grid); } else { - selectAll(grid) + RangeSelectionHelper.selectAll(grid); } } - var isSameRange = function (range, otherRange) { - return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell && - range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow; - }; - - function getRangeOfWholeGrid(grid) { - return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1); - } - - function deselect(grid) { + function selectNone(grid) { var selectionModel = grid.getSelectionModel(); selectionModel.setSelectedRanges([]); } - function selectAll(grid) { - var range = getRangeOfWholeGrid(grid); - var selectionModel = grid.getSelectionModel(); - - selectionModel.setSelectedRanges([range]); - } - $.extend(this, { "init": init, "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes diff --git a/web/pgadmin/static/js/selection/range_boundary_navigator.js b/web/pgadmin/static/js/selection/range_boundary_navigator.js index a268d245..f88a6533 100644 --- a/web/pgadmin/static/js/selection/range_boundary_navigator.js +++ b/web/pgadmin/static/js/selection/range_boundary_navigator.js @@ -1,4 +1,5 @@ -define(['sources/selection/range_selection_helper'], function (RangeSelectionHelper) { +define(['sources/selection/range_selection_helper'], +function (RangeSelectionHelper) { return { getUnion: function (allRanges) { if (_.isEmpty(allRanges)) { @@ -77,6 +78,10 @@ define(['sources/selection/range_selection_helper'], function (RangeSelectionHel removeFirstColumn: function (colRangeBounds) { var unionedColRanges = this.getUnion(colRangeBounds); + if(unionedColRanges.length == 0) { + return []; + } + var firstSubrangeStartsAt0 = function () { return unionedColRanges[0][0] == 0; }; diff --git a/web/pgadmin/static/js/selection/range_selection_helper.js b/web/pgadmin/static/js/selection/range_selection_helper.js index 31ad3bf7..6c69c390 100644 --- a/web/pgadmin/static/js/selection/range_selection_helper.js +++ b/web/pgadmin/static/js/selection/range_selection_helper.js @@ -45,7 +45,7 @@ define(['slickgrid'], function () { return new Slick.Range(rowId, 1, rowId, grid.getColumns().length - 1); }; - function rangeForColumn(grid, columnIndex) { + var rangeForColumn = function (grid, columnIndex) { return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex) }; @@ -63,6 +63,13 @@ define(['slickgrid'], function () { return !_.isUndefined(columnDefinitions[0].pos); }; + function selectAll(grid) { + var range = getRangeOfWholeGrid(grid); + var selectionModel = grid.getSelectionModel(); + + selectionModel.setSelectedRanges([range]); + } + return { addRange: addRange, removeRange: removeRange, @@ -73,6 +80,7 @@ define(['slickgrid'], function () { rangeForColumn: rangeForColumn, isEntireGridSelected: isEntireGridSelected, getRangeOfWholeGrid: getRangeOfWholeGrid, - isFirstColumnData: isFirstColumnData + isFirstColumnData: isFirstColumnData, + selectAll: selectAll, } }); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/row_selector.js b/web/pgadmin/static/js/selection/row_selector.js index 76a8c1a7..8124b5cb 100644 --- a/web/pgadmin/static/js/selection/row_selector.js +++ b/web/pgadmin/static/js/selection/row_selector.js @@ -19,7 +19,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func } updateRanges(grid, args.row); } - } + }; var handleSelectedRangesChanged = function (grid, event, ranges) { $('[data-cell-type="row-header-checkbox"]:checked') @@ -32,7 +32,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func toggleCheckbox($checkbox); } }); - } + }; var updateRanges = function (grid, rowId) { var selectionModel = grid.getSelectionModel(); @@ -81,5 +81,6 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes }); }; + return RowSelector; }); diff --git a/web/pgadmin/static/js/slickgrid/cell_selector.js b/web/pgadmin/static/js/slickgrid/cell_selector.js new file mode 100644 index 00000000..a709351e --- /dev/null +++ b/web/pgadmin/static/js/slickgrid/cell_selector.js @@ -0,0 +1,18 @@ +define(["slickgrid"], function () { + var Slick = window.Slick; + + return function () { + this.init = function (grid) { + grid.onActiveCellChanged.subscribe(function (event, slickEvent) { + grid.getSelectionModel().setSelectedRanges([ + new Slick.Range( + slickEvent.row, + slickEvent.cell, + slickEvent.row, + slickEvent.cell + ) + ]); + }); + } + } +}); \ No newline at end of file diff --git a/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js b/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js new file mode 100644 index 00000000..2a319407 --- /dev/null +++ b/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js @@ -0,0 +1,21 @@ +define([ + 'sources/selection/copy_data', + 'sources/selection/range_selection_helper' + ], +function (copyData, RangeSelectionHelper) { + return function handleQueryOutputKeyboardEvent(event, args) { + var KEY_C = 67; + var KEY_A = 65; + var modifiedKey = event.keyCode; + var isModifierDown = event.ctrlKey || event.metaKey; + this.slickgrid = args.grid; + + if (isModifierDown && modifiedKey == KEY_C) { + copyData.apply(this); + } + + if (isModifierDown && modifiedKey == KEY_A) { + RangeSelectionHelper.selectAll(this.slickgrid); + } + } +}); \ No newline at end of file diff --git a/web/pgadmin/static/vendor/slickgrid/README b/web/pgadmin/static/vendor/slickgrid/README deleted file mode 100644 index cf583515..00000000 --- a/web/pgadmin/static/vendor/slickgrid/README +++ /dev/null @@ -1,9 +0,0 @@ -WARNING!! - -The following changes have been made to SlickGrid. These must be re-applied if updating the code from upstream! - -- The CSS class 'slick-row' has been renamed to 'sr' - -- The CSS class 'slick-cell' has been renamed to 'sc' - -The intent of these changes is to minimise memory usage by the grid, by saving a few bytes per row/cell. \ No newline at end of file diff --git a/web/pgadmin/static/vendor/slickgrid/README.md b/web/pgadmin/static/vendor/slickgrid/README.md new file mode 100644 index 00000000..e8a1ad7a --- /dev/null +++ b/web/pgadmin/static/vendor/slickgrid/README.md @@ -0,0 +1,9 @@ +## This is the 6pac SlickGrid repo + +This is the acknowledged most active non-customised fork of SlickGrid. + +It aims to be a viable alternative master repo, building on the legacy of the mleibman/SlickGrid master branch, keeping libraries up to date and applying small, safe core patches and enhancements without turning into a personalised build. + +Check out the [examples](https://github.com/6pac/SlickGrid/wiki/Examples) for examples demonstrating new features and use cases, such as dynamic grid creation and editors with third party controls. + +Also check out the [wiki](https://github.com/6pac/SlickGrid/wiki) for news and documentation. \ No newline at end of file diff --git a/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js b/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js index dc167209..93a6be5b 100644 --- a/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js +++ b/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js @@ -12,12 +12,12 @@ grid.onColumnsReordered.subscribe(updateColumnOrder); options = $.extend({}, defaults, options); - $menu = $("