From e19db25553a6cf8fb7f6d728b6c2e885c499f6bd Mon Sep 17 00:00:00 2001 From: Ishan Taldekar Date: Thu, 20 Feb 2025 15:19:14 -0500 Subject: [PATCH] Mynah UI fixes (#379) --- .../eclipse/amazonq/chat/ChatTheme.java | 4 + .../amazonq/chat/models/QChatCssVariable.java | 5 +- .../amazonq/views/AmazonQChatWebview.java | 182 +++++++++++++++++- .../chat/models/QChatCssVariableTest.java | 7 +- 4 files changed, 194 insertions(+), 4 deletions(-) diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java index 1c031f598..6308426dd 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatTheme.java @@ -100,6 +100,8 @@ private String getCssForDarkTheme() { // Card themeMap.put(QChatCssVariable.CardBackground, cardBackgroundColor); + themeMap.put(QChatCssVariable.LineHeight, "1.25em"); + return getCss(themeMap); } @@ -150,6 +152,8 @@ private String getCssForLightTheme() { // Card themeMap.put(QChatCssVariable.CardBackground, cardBackgroundColor); + themeMap.put(QChatCssVariable.LineHeight, "1.25em"); + return getCss(themeMap); } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java index a57c7ce26..14bcd1b38 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariable.java @@ -44,7 +44,10 @@ public enum QChatCssVariable { AlternateForeground("--mynah-color-alternate-reverse"), // Card - CardBackground("--mynah-card-bg"); + CardBackground("--mynah-card-bg"), + + // Line height + LineHeight("--mynah-line-height"); private String value; diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java index ebe4b33d5..2de160e77 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatWebview.java @@ -3,6 +3,8 @@ package software.aws.toolkits.eclipse.amazonq.views; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; import java.util.List; import java.util.Optional; @@ -26,6 +28,8 @@ import software.aws.toolkits.eclipse.amazonq.lsp.model.QuickActionsCommandGroup; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; import software.aws.toolkits.eclipse.amazonq.util.ObjectMapperFactory; +import software.aws.toolkits.eclipse.amazonq.util.PluginPlatform; +import software.aws.toolkits.eclipse.amazonq.util.PluginUtils; import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils; import software.aws.toolkits.eclipse.amazonq.views.actions.AmazonQCommonActions; @@ -91,6 +95,7 @@ public void completed(final ProgressEvent event) { amazonQCommonActions = getAmazonQCommonActions(); chatCommunicationManager.setChatUiRequestListener(this); + new BrowserFunction(browser, "ideCommand") { @Override public Object function(final Object[] arguments) { @@ -101,6 +106,24 @@ public Object function(final Object[] arguments) { } }; + new BrowserFunction(browser, "isMacOs") { + @Override + public Object function(final Object[] arguments) { + return Boolean.TRUE.equals(PluginUtils.getPlatform() == PluginPlatform.MAC); + } + }; + + new BrowserFunction(browser, "copyToClipboard") { + @Override + public Object function(final Object[] arguments) { + if (arguments.length > 0 && arguments[0] instanceof String) { + StringSelection stringSelection = new StringSelection((String) arguments[0]); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + } + return null; + } + }; + // Inject chat theme after mynah-ui has loaded browser.addProgressListener(new ProgressAdapter() { @Override @@ -210,6 +233,16 @@ private String generateCss() { mask-position: center; scale: 60%; } + .code-snippet-close-button i.mynah-ui-icon-cancel, + .mynah-chat-item-card-related-content-show-more i.mynah-ui-icon-down-open { + -webkit-mask-size: 195.5% !important; + mask-size: 195.5% !important; + mask-position: center; + aspect-ratio: 1/1; + width: 15px; + height: 15px; + scale: 50% + } .mynah-ui-icon-tabs { -webkit-mask-size: 102% !important; mask-size: 102% !important; @@ -218,6 +251,9 @@ private String generateCss() { textarea:placeholder-shown { line-height: 1.5rem; } + .mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part > .mynah-ui-spinner-logo-mask.text { + opacity: 1 !important; + } """; } @@ -236,7 +272,8 @@ private String generateJS(final String jsEntrypoint) { postMessage: (message) => { ideCommand(JSON.stringify(message)); } - }, { + }, + { quickActionCommands: %s, disclaimerAcknowledged: %b }); @@ -245,9 +282,19 @@ private String generateJS(final String jsEntrypoint) { } window.addEventListener('load', init); + + %s + + %s + + %s + + %s + """, jsEntrypoint, getWaitFunction(), chatQuickActionConfig, - "true".equals(disclaimerAcknowledged)); + "true".equals(disclaimerAcknowledged), getArrowKeyBlockingFunction(), + getSelectAllAndCopySupportFunctions(), getPreventEmptyPopupFunction(), getFocusOnChatPromptFunction()); } /* @@ -271,6 +318,137 @@ private String serializeQuickActionCommands(final List } } + private String getArrowKeyBlockingFunction() { + return """ + window.addEventListener('load', () => { + const textarea = document.querySelector('textarea.mynah-chat-prompt-input'); + if (textarea) { + textarea.addEventListener('keydown', (event) => { + const cursorPosition = textarea.selectionStart; + const hasText = textarea.value.length > 0; + + // block arrow keys on empty text area + switch (event.key) { + case 'ArrowLeft': + if (!hasText || cursorPosition === 0) { + event.preventDefault(); + event.stopPropagation(); + } + break; + + case 'ArrowRight': + if (!hasText || cursorPosition === textarea.value.length) { + event.preventDefault(); + event.stopPropagation(); + } + break; + } + }); + } + }); + """; + } + + private String getSelectAllAndCopySupportFunctions() { + return """ + window.addEventListener('load', () => { + const textarea = document.querySelector('textarea.mynah-chat-prompt-input'); + if (textarea) { + textarea.addEventListener("keydown", (event) => { + if (((isMacOs() && event.metaKey) || (!isMacOs() && event.ctrlKey)) + && event.key === 'a') { + textarea.select(); + event.preventDefault(); + event.stopPropagation(); + } + }); + } + }); + + window.addEventListener('load', () => { + const textarea = document.querySelector('textarea.mynah-chat-prompt-input'); + if (textarea) { + textarea.addEventListener("keydown", (event) => { + if (((isMacOs() && event.metaKey) || (!isMacOs() && event.ctrlKey)) + && event.key === 'c') { + copyToClipboard(textarea.value); + event.preventDefault(); + event.stopPropagation(); + } + }); + } + }); + """; + } + + private String getPreventEmptyPopupFunction() { + String selector = ".mynah-button" + ".mynah-button-secondary.mynah-button-border" + ".fill-state-always" + + ".mynah-chat-item-followup-question-option" + ".mynah-ui-clickable-item"; + + return """ + const observer = new MutationObserver((mutations) => { + try { + const selector = '%s'; + + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { // Check if it's an element node + // Check for direct match + if (node.matches && node.matches(selector)) { + attachEventListeners(node); + } + // Check for nested matches + if (node.querySelectorAll) { + const buttons = node.querySelectorAll(selector); // Missing selector parameter + buttons.forEach(attachEventListeners); + } + } + }); + }); + } catch (error) { + console.error('Error in mutation observer:', error); + } + }); + + function attachEventListeners(element) { + if (!element || element.dataset.hasListener) return; // Prevent duplicate listeners + + const handleMouseOver = function(event) { + const textSpan = this.querySelector('span.mynah-button-label'); + if (textSpan && textSpan.scrollWidth <= textSpan.offsetWidth) { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + } + }; + + element.addEventListener('mouseover', handleMouseOver, true); + element.dataset.hasListener = 'true'; + } + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true + }); + """.formatted(selector); + } + + private String getFocusOnChatPromptFunction() { + return """ + window.addEventListener('load', () => { + const chatContainer = document.querySelector('.mynah-chat-prompt'); + if (chatContainer) { + chatContainer.addEventListener('click', (event) => { + if (!event.target.closest('.mynah-chat-prompt-input')) { + keepFocusOnPrompt(); + } + }); + } + }); + """; + } + @Override public final void onSendToChatUi(final String message) { String script = "window.postMessage(" + message + ");"; diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java index 6ec137cfc..45ae883ef 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/chat/models/QChatCssVariableTest.java @@ -14,7 +14,7 @@ public class QChatCssVariableTest { @Test void testEnumValues() { - assertEquals(27, QChatCssVariable.values().length); + assertEquals(28, QChatCssVariable.values().length); } @Test @@ -72,6 +72,11 @@ void testCardValues() { assertEquals("--mynah-card-bg", QChatCssVariable.CardBackground.getValue()); } + @Test + void testLineHeighValues() { + assertEquals("--mynah-line-height", QChatCssVariable.LineHeight.getValue()); + } + @Test void testInvalidEnum() { assertThrows(IllegalArgumentException.class, () -> QChatCssVariable.valueOf("NonExistentVariable"));