diff --git a/object_database/web/cells/button.py b/object_database/web/cells/button.py index 516583fa..a77693e3 100644 --- a/object_database/web/cells/button.py +++ b/object_database/web/cells/button.py @@ -49,9 +49,14 @@ def onMessage(self, msgFrame): self.parent.childHadUserAction(self, self) if isinstance(redirectValue, str): - self.scheduleMessage( - {"action": "redirect", "url": redirectValue, "target": self.target} - ) + if msgFrame.get("event") == "ctrlClick": + self.scheduleMessage( + {"action": "redirect", "url": redirectValue, "target": "_blank"} + ) + else: + self.scheduleMessage( + {"action": "redirect", "url": redirectValue, "target": self.target} + ) class Button(Clickable): diff --git a/object_database/web/cells_demo/clickable.py b/object_database/web/cells_demo/clickable.py index 00b95667..91cc335a 100644 --- a/object_database/web/cells_demo/clickable.py +++ b/object_database/web/cells_demo/clickable.py @@ -15,6 +15,9 @@ from object_database.web import cells as cells from object_database.web.CellsTestPage import CellsTestPage +from selenium.webdriver import ActionChains +from selenium.webdriver.common.keys import Keys + class ClickableText(CellsTestPage): def cell(self): @@ -43,7 +46,8 @@ def test_clickable_clicking_works(headless_browser): demo_root.click() headless_browser.wait(2).until( headless_browser.expect.text_to_be_present_in_element( - (headless_browser.by.CSS_SELECTOR, query), "You've clicked on this text 1 times" + (headless_browser.by.CSS_SELECTOR, query), + "You've clicked on this text 1 times", ) ) @@ -89,3 +93,85 @@ def cell(self): def text(self): return "You should see a clickable with highlighted text under it." + + +class LinkTextCtrlClick(CellsTestPage): + def cell(self): + slot = cells.Slot(0) + return cells.Clickable( + "www.google.com", lambda: "https://www.google.com" + ) + cells.Button( + cells.Subscribed(lambda: f"Clicked {slot.get()} times"), + lambda: slot.set(slot.get() + 1), + ) + + def text(self): + return ( + "You should see some link-text that opens in the same tab. Clicking " + + "while holding the CTRL key will open the link in a new tab. " + + "Control-clicking the button will work as normal." + ) + + +def test_click_opens_in_new_tab(headless_browser): + # Close all other windows + for window in headless_browser.window_handles[1:]: + headless_browser.switch_to_window(window) + headless_browser.close() + headless_browser.switch_to_window(headless_browser.window_handles[0]) + assert len(headless_browser.window_handles) == 1 + + # clicking opens in the same tab + demo_root = headless_browser.get_demo_root_for(LinkTextCtrlClick) + assert demo_root + assert demo_root.get_attribute("data-cell-type") == "Sequence" + query = "[data-cell-type='Clickable']" + link = headless_browser.find_by_css(query) + assert link.text == "www.google.com" + assert len(headless_browser.window_handles) == 1 + link.click() + headless_browser.wait(10).until(headless_browser.expect.number_of_windows_to_be(1)) + assert len(headless_browser.window_handles) == 1 + assert headless_browser.current_url == "https://www.google.com/" + + # ctrl clicking opens in a new tab + demo_root = headless_browser.get_demo_root_for(LinkTextCtrlClick) + assert demo_root + assert demo_root.get_attribute("data-cell-type") == "Sequence" + query = "[data-cell-type='Clickable']" + link = headless_browser.find_by_css(query) + assert link.text == "www.google.com" + action = ActionChains(headless_browser.webdriver) + action.key_down(Keys.CONTROL).click(link).key_up(Keys.CONTROL).perform() + headless_browser.wait(10).until(headless_browser.expect.number_of_windows_to_be(2)) + assert len(headless_browser.window_handles) == 2 + headless_browser.switch_to_window(headless_browser.window_handles[1]) + assert headless_browser.current_url == "https://www.google.com/" + + # cleanup + for window in headless_browser.window_handles[1:]: + headless_browser.switch_to_window(window) + headless_browser.close() + headless_browser.switch_to_window(headless_browser.window_handles[0]) + assert len(headless_browser.window_handles) == 1 + + +def test_control_click_doesnt_affect_buttons(headless_browser): + """control-clicking bumps the slot, but doesn't open new tabs.""" + query = "[data-cell-type='Button']" + + button = headless_browser.find_by_css(query) + assert button.text == "Clicked 0 times" + + # ctrl+click + action = ActionChains(headless_browser.webdriver) + action.key_down(Keys.CONTROL).click(button).key_up(Keys.CONTROL).perform() + + headless_browser.wait(2).until( + headless_browser.expect.text_to_be_present_in_element( + (headless_browser.by.CSS_SELECTOR, query), "Clicked 1 times" + ) + ) + + # no new tab generated + assert len(headless_browser.window_handles) == 1 diff --git a/object_database/web/cells_demo/conftest.py b/object_database/web/cells_demo/conftest.py index 5b1711b3..816cc788 100644 --- a/object_database/web/cells_demo/conftest.py +++ b/object_database/web/cells_demo/conftest.py @@ -166,7 +166,7 @@ def bootup_webdriver(): # options.binary_location = '/usr/bin/google-chrome' # driver = webdriver.Chrome(executable_path=chrome_path, chrome_options=options) - # start upu the driver + # start up the driver driver = webdriver.Chrome( options=options, desired_capabilities={"goog:loggingPrefs": {"browser": "ALL"}} ) @@ -349,6 +349,9 @@ def current_url(self): def switch_to_window(self, handle): return self.webdriver.switch_to.window(handle) + def close(self): + return self.webdriver.close() + @pytest.fixture(scope="session") def headless_browser(): diff --git a/object_database/web/content/src/components/Clickable.js b/object_database/web/content/src/components/Clickable.js index 44dda752..6f68bf74 100644 --- a/object_database/web/content/src/components/Clickable.js +++ b/object_database/web/content/src/components/Clickable.js @@ -66,13 +66,21 @@ class Clickable extends ConcreteCell { event.stopPropagation(); if (this.props.url) { - if (this.props.target) { + if (event.ctrlKey) { + window.open(this.props.url, "_blank"); + } + else if (this.props.target) { window.open(this.props.url, this.props.target); } else { window.location.href = this.props.url; } } else { - this.sendMessage({'event': 'click'}); + if (event.ctrlKey) { + this.sendMessage({'event': 'ctrlClick'}); + } + else { + this.sendMessage({'event': 'click'}); + } } }