Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style AppMap webviews to match the IDE's theme #862

Merged
merged 2 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appland-findings/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./ide-styles.css" />

<title>AppLand Findings</title>
</head>
Expand Down
1 change: 1 addition & 0 deletions appland-install-guide/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AppLand Installation Guide</title>
<link rel="stylesheet" href="./ide-styles.css" />
<style>
.recording-method__ask-navie-button {
display: none;
Expand Down
5 changes: 0 additions & 5 deletions appland-navie/dist/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -363302,11 +363302,6 @@ ${t5}`));
}
});
});
messages2.on("navie-restarting", () => {
app.$refs.ui.onNavieRestarting();
}).on("navie-restarted", () => {
app.$refs.ui.loadNavieConfig();
});
app.$on("choose-files-to-pin", () => vsCodeBridge_default.postMessage({ command: "choose-files-to-pin" }));
app.$on("click-link", (link2) => vsCodeBridge_default.postMessage({ command: "click-link", link: link2 }));
app.$on("open-install-instructions", () => vsCodeBridge_default.postMessage({ command: "open-install-instructions" }));
Expand Down
2 changes: 1 addition & 1 deletion appland-navie/dist/main.js.map

Large diffs are not rendered by default.

25 changes: 22 additions & 3 deletions appland-navie/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AppMap AI: Explain</title>
<link rel="stylesheet" href="./dist/main.css" />
<link rel="stylesheet" href="./ide-styles.css" />
<!--suppress CssUnusedSymbol -->
<style>
body {
font-family: system-ui, sans-serif;
Expand Down Expand Up @@ -49,9 +51,7 @@
border: 1px solid #2d3546;
}

/*
The baseline of the chat input is too high. This makes it a lot less obvious.
*/
/* The baseline of the chat input is too high. This makes it a lot less obvious. */
[data-cy="chat-input"] {
padding-top: 0.9rem !important;
}
Expand All @@ -60,6 +60,25 @@
[data-cy="save-message"] {
display: none !important;
}

/* Styling of the text in Navie's "Pinned Items" */
/*noinspection CssUnresolvedCustomProperty*/
.intellij-notice p {
color: var(--appmap-color-foreground) !important;
}

/* Style the appmap icon in Navie */
/*noinspection CssUnresolvedCustomProperty*/
.context__body__table-row__header svg path {
fill: var(--appmap-color-foreground);
}

/* override monospace font for code snippets */
/*noinspection CssUnresolvedCustomProperty*/
.message .message-body code {
font-family: var(--appmap-font-code-family), monospace !important;
font-size: var(--appmap-font-code-size) !important;
}
</style>
</head>

Expand Down
5 changes: 1 addition & 4 deletions appland-navie/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
},
"dependencies": {
"@appland/components": "^4.44.0",
"highlight.js": "^11.9.0",
"highlight.js": "^11.11.1",
"url": "^0.11",
"vue": "^2.7",
"vue-template-compiler": "^2.7"
},
"resolutions": {
"@appland/rpc": "1.17.0"
}
}
30 changes: 15 additions & 15 deletions appland-navie/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ __metadata:
languageName: node
linkType: hard

"@appland/rpc@npm:1.17.0":
"@appland/rpc@npm:^1.13.0":
version: 1.17.0
resolution: "@appland/rpc@npm:1.17.0"
dependencies:
Expand Down Expand Up @@ -800,7 +800,7 @@ __metadata:
"@appland/components": "npm:^4.44.0"
browserify-fs: "npm:^1.0.0"
buffer: "npm:^6.0.3"
highlight.js: "npm:^11.9.0"
highlight.js: "npm:^11.11.1"
node-libs-browser: "npm:^2.2.1"
process: "npm:^0.11.10"
tsup: "npm:^8.2.4"
Expand Down Expand Up @@ -2160,9 +2160,9 @@ __metadata:
linkType: hard

"exponential-backoff@npm:^3.1.1":
version: 3.1.1
resolution: "exponential-backoff@npm:3.1.1"
checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579
version: 3.1.2
resolution: "exponential-backoff@npm:3.1.2"
checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844
languageName: node
linkType: hard

Expand Down Expand Up @@ -2396,7 +2396,7 @@ __metadata:
languageName: node
linkType: hard

"highlight.js@npm:^11.9.0":
"highlight.js@npm:^11.11.1, highlight.js@npm:^11.9.0":
version: 11.11.1
resolution: "highlight.js@npm:11.11.1"
checksum: 10c0/40f53ac19dac079891fcefd5bd8a21cf2e8931fd47da5bd1dca73b7e4375c1defed0636fc39120c639b9c44119b7d110f7f0c15aa899557a5a1c8910f3c0144c
Expand Down Expand Up @@ -3396,8 +3396,8 @@ __metadata:
linkType: hard

"node-gyp@npm:latest":
version: 11.0.0
resolution: "node-gyp@npm:11.0.0"
version: 11.1.0
resolution: "node-gyp@npm:11.1.0"
dependencies:
env-paths: "npm:^2.2.0"
exponential-backoff: "npm:^3.1.1"
Expand All @@ -3411,7 +3411,7 @@ __metadata:
which: "npm:^5.0.0"
bin:
node-gyp: bin/node-gyp.js
checksum: 10c0/a3b885bbee2d271f1def32ba2e30ffcf4562a3db33af06b8b365e053153e2dd2051b9945783c3c8e852d26a0f20f65b251c7e83361623383a99635c0280ee573
checksum: 10c0/c38977ce502f1ea41ba2b8721bd5b49bc3d5b3f813eabfac8414082faf0620ccb5211e15c4daecc23ed9f5e3e9cc4da00e575a0bcfc2a95a069294f2afa1e0cd
languageName: node
linkType: hard

Expand Down Expand Up @@ -4023,11 +4023,11 @@ __metadata:
linkType: hard

"semver@npm:^7.3.5":
version: 7.6.3
resolution: "semver@npm:7.6.3"
version: 7.7.1
resolution: "semver@npm:7.7.1"
bin:
semver: bin/semver.js
checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf
checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958
languageName: node
linkType: hard

Expand Down Expand Up @@ -4185,12 +4185,12 @@ __metadata:
linkType: hard

"socks@npm:^2.8.3":
version: 2.8.3
resolution: "socks@npm:2.8.3"
version: 2.8.4
resolution: "socks@npm:2.8.4"
dependencies:
ip-address: "npm:^9.0.5"
smart-buffer: "npm:^4.2.0"
checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7
checksum: 10c0/00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5
languageName: node
linkType: hard

Expand Down
1 change: 1 addition & 0 deletions appland/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./ide-styles.css" />
<title>AppLand Scenario</title>
</head>
<body>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package appland.webviews;

import appland.webviews.webserver.AppMapWebview;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.intellij.ui.jcef.JBCefScrollbarsHelper;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefCallback;
import org.cef.handler.CefRequestHandlerAdapter;
import org.cef.handler.CefResourceHandler;
import org.cef.handler.CefResourceRequestHandler;
import org.cef.handler.CefResourceRequestHandlerAdapter;
import org.cef.misc.BoolRef;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefResponse;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

public class IdeStyleResourceHandler extends CefRequestHandlerAdapter {
@Override
public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser,
CefFrame frame,
CefRequest request,
boolean isNavigation,
boolean isDownload,
String requestInitiator,
BoolRef disableDefaultHandling) {

if (isIdeStylesRequest(request)) {
var ideStylesStream = new ByteArrayInputStream(createIdeStyles().getBytes(StandardCharsets.UTF_8));

return new CefResourceRequestHandlerAdapter() {
@Override
public CefResourceHandler getResourceHandler(CefBrowser browser, CefFrame frame, CefRequest request) {
return new CefResourceHandler() {
@Override
public boolean processRequest(CefRequest cefRequest, CefCallback cefCallback) {
cefCallback.Continue();
return true;
}

@Override
public void getResponseHeaders(CefResponse cefResponse, IntRef intRef, StringRef stringRef) {
cefResponse.setMimeType("text/css");
cefResponse.setStatus(200);
}

@Override
public boolean readResponse(byte[] bytes, int bytesToRead, IntRef bytesRead, CefCallback cefCallback) {
try {
bytesRead.set(ideStylesStream.read(bytes, 0, bytesToRead));
if (bytesRead.get() != -1) {
return true;
}
} catch (Exception e) {
cefCallback.cancel();
}
bytesRead.set(0);
return false;
}

@Override
public void cancel() {
// empty
}
};
}
};
}

// fallback to default handling of JCEF
return null;
}

// see https://github.com/getappmap/vscode-appland/blob/bfd83ad8c848d31257ab004688eb847feecbcf32/web/static/styles/navie-integration.css#L1-L0
private @NotNull String createIdeStyles() {
var scheme = EditorColorsManager.getInstance().getGlobalScheme();

var background = UIUtil.getPanelBackground();

// used for glow and border around Navie's main text input box
var highlight = UIUtil.getFocusedBorderColor();
var highlightLight = highlight.brighter();
var highlightDark = highlight.darker();

// text color
var foreground = UIUtil.getLabelForeground();
var foregroundSecondary = UIUtil.getLabelDisabledForeground();
var foregroundLight = foreground.brighter();
var foregroundDark = foreground.darker();

var linkColor = JBUI.CurrentTheme.Link.Foreground.ENABLED;
var linkColorHover = JBUI.CurrentTheme.Link.Foreground.HOVERED;

var error = JBUI.CurrentTheme.Focus.errorColor(true);
var warning = JBUI.CurrentTheme.Focus.warningColor(true);
var success = JBColor.GREEN.brighter();

var fontFamily = UISettings.getInstance().getFontFace();
var fontSize = UISettings.getInstance().getFontSize() + "px";
var fontWeight = "normal";

var fontCodeFamily = scheme.getEditorFontName();
var fontCodeSize = String.format("%.2fpx", scheme.getEditorFontSize2D());

var buttonBackground = JBUI.CurrentTheme.Button.defaultButtonColorStart();
var buttonBackgroundHover = JBUI.CurrentTheme.Button.focusBorderColor(true);
var buttonForeground = foregroundContrastColor(buttonBackground);

var inputBackground = UIUtil.getTextFieldBackground();
var inputForeground = UIUtil.getTextFieldForeground();

// AppMap is using "1px solid var(--appmap-color-border,rgba(255,255,255,.1))" at some places,
// we're applying the same alpha value to make it fit.
var border = ColorUtil.withAlpha(JBUI.CurrentTheme.List.buttonSeparatorColor(), 0.1);
var selection = JBUI.CurrentTheme.List.Selection.foreground(true);

var colorTileBackground = JBColor.DARK_GRAY;
var colorTileShadow = JBColor.LIGHT_GRAY;

// apply the global scale factor to the webview
var ideStyles = "html { transform: scale(" + String.format(Locale.ENGLISH, "%.3f", JBUIScale.scale(1.0f)) + "); }";

// CSS colors based on the current theme
var ideThemeColors = ":root {\n" +
" --appmap-color-background: " + toCssColor(background) + ";\n" +
" --appmap-color-highlight: " + toCssColor(highlight) + ";\n" +
" --appmap-color-highlight-light: " + toCssColor(highlightLight) + ";\n" +
" --appmap-color-highlight-dark: " + toCssColor(highlightDark) + ";\n" +
" --appmap-color-input-bg: " + toCssColor(inputBackground) + ";\n" +
" --appmap-color-input-fg: " + toCssColor(inputForeground) + ";\n" +
" --appmap-color-button-fg: " + toCssColor(buttonForeground) + ";\n" +
" --appmap-color-button-bg: " + toCssColor(buttonBackground) + ";\n" +
" --appmap-color-button-bg-hover: " + toCssColor(buttonBackgroundHover) + ";\n" +
" --appmap-color-selection: " + toCssColor(selection) + ";\n" +
" --appmap-color-border: " + toCssColor(border) + ";\n" +
" --appmap-color-foreground: " + toCssColor(foreground) + ";\n" +
" --appmap-color-foreground-secondary: " + toCssColor(foregroundSecondary) + ";\n" +
" --appmap-color-foreground-light: " + toCssColor(foregroundLight) + ";\n" +
" --appmap-color-foreground-dark: " + toCssColor(foregroundDark) + ";\n" +
" --appmap-font-family: " + fontFamily + ";\n" +
" --appmap-font-size: " + fontSize + ";\n" +
" --appmap-font-weight: " + fontWeight + ";\n" +
" --appmap-font-code-family: " + fontCodeFamily + ";\n" + // custom property
" --appmap-font-code-size: " + fontCodeSize + ";\n" + // custom property
" --appmap-color-success: " + toCssColor(success) + ";\n" +
" --appmap-color-error: " + toCssColor(error) + ";\n" +
" --appmap-color-warning: " + toCssColor(warning) + ";\n" +
" --appmap-color-link: " + toCssColor(linkColor) + ";\n" +
" --appmap-color-link-hover: " + toCssColor(linkColorHover) + ";\n" +
" --appmap-color-tile-background: " + toCssColor(colorTileBackground) + ";\n" +
" --appmap-color-tile-shadow: " + toCssColor(colorTileShadow) + ";\n" +
"}\n";

return JBCefScrollbarsHelper.buildScrollbarsStyle() + "\n" + ideThemeColors + "\n" + ideStyles;
}

/**
* @return true if the request is for a webview asset, but not yet signed with an auth token
*/
private static boolean isIdeStylesRequest(CefRequest request) {
var url = request.getURL();
return url.startsWith(AppMapWebview.getBaseUrlWithPath()) && url.endsWith("/ide-styles.css");
}

private @NotNull String toCssColor(@NotNull Color color) {
return String.format(Locale.ENGLISH,
"rgba(%d, %d, %d, %.3f)",
color.getRed(),
color.getGreen(),
color.getBlue(),
(double) color.getAlpha() / 255.0);
}

private @NotNull Color foregroundContrastColor(@NotNull Color background) {
return contrastColor(background, JBColor.WHITE, JBColor.BLACK);
}

private @NotNull Color contrastColor(@NotNull Color background, @NotNull Color first, @NotNull Color second) {
if (ColorUtil.calculateContrastRatio(background, first) > ColorUtil.calculateContrastRatio(background, second)) {
return first;
}
return second;
}
}
2 changes: 2 additions & 0 deletions plugin-core/src/main/java/appland/webviews/WebviewEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ protected boolean isWebViewReady() {
}

private void setupJCEF() {
// provide CSS styles customized to match the current IDE
contentPanel.getJBCefClient().addRequestHandler(new IdeStyleResourceHandler(), contentPanel.getCefBrowser());
// open links to https://appmap.io in the external browser
contentPanel.getJBCefClient().addRequestHandler(new OpenExternalLinksHandler(), contentPanel.getCefBrowser());
// open new webview windows, which are opened via <a href="..." target="_blank", in the external browser
Expand Down