Skip to content

feat: DH-19307: External theme support#2425

Merged
bmingles merged 41 commits intodeephaven:mainfrom
bmingles:DH-19307_external-theme-support
May 9, 2025
Merged

feat: DH-19307: External theme support#2425
bmingles merged 41 commits intodeephaven:mainfrom
bmingles:DH-19307_external-theme-support

Conversation

@bmingles
Copy link
Copy Markdown
Contributor

@bmingles bmingles commented Apr 30, 2025

DH-19307: External theme support via postMessage apis

  • Moved common MessageUtils into @deephaven/utils
  • External themes can be enabled by setting theme=[EXTERNAL_THEME_KEY] query string param
  • ThemeBootstrap now checks if external themes are enabled. If so, the theme is requested from the parent Window via MSG_REQUEST_GET_THEME postMessage
  • Current or parent Window can explicitly set external theme via MSG_REQUEST_SET_THEME postMessage

Testing

You can download this html file: https://gist.github.com/bmingles/02451e09d218cdbe61d8c506e556562b . You should be able to open it in browser from local filesystem, no need for server.

  1. Start DH locally in this branch (assumes port 4000)
  2. Open the html file
  3. Initial load should show red background color showing DH requesting theme from the parent
  4. Clicking the "Update Theme" button in bottom left should change to random bg color. Demonstrates explicit setting of theme from parent Window

@bmingles bmingles changed the title WIP: parent theme support (DH-19307) feat: WIP: parent theme support (DH-19307) Apr 30, 2025
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2025

Codecov Report

Attention: Patch coverage is 96.50350% with 5 lines in your changes missing coverage. Please review.

Project coverage is 47.23%. Comparing base (6d65594) to head (bc94a2b).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
packages/utils/src/MessageUtils.ts 95.91% 2 Missing ⚠️
packages/components/src/theme/ThemeProvider.tsx 66.66% 1 Missing ⚠️
packages/components/src/theme/useExternalTheme.ts 96.42% 1 Missing ⚠️
...ckages/jsapi-components/src/useBroadcastChannel.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2425      +/-   ##
==========================================
+ Coverage   47.06%   47.23%   +0.16%     
==========================================
  Files         714      718       +4     
  Lines       39468    39594     +126     
  Branches    10063    10098      +35     
==========================================
+ Hits        18576    18701     +125     
+ Misses      20881    20839      -42     
- Partials       11       54      +43     
Flag Coverage Δ
unit 47.23% <96.50%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the functions in this module were pre-existing and moved from @deephaven/app-util. The only new function is sendMessageToParent

@bmingles bmingles changed the title feat: WIP: parent theme support (DH-19307) feat: DH-19307: External theme support May 5, 2025
@bmingles bmingles marked this pull request as ready for review May 5, 2025 17:29
@bmingles bmingles requested a review from mofojed May 5, 2025 18:09
@bmingles bmingles force-pushed the DH-19307_external-theme-support branch from ee8ffad to 0e3a4f8 Compare May 5, 2025 20:44

return (
<ThemeProvider themes={themes}>
<ThemeProvider themes={themes} waitForActivation={waitForActivation}>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to pass this waitForActivation logic down to ThemeProvider, and we don't need the waitForActivation here.
We're already doing themes = useCustomThemes which internally is already waiting for the external theme if it's activated.
I would think at most here we'd need:

{themes != null && <ThemeProvider> ...}

Though maybe I'm missing something.

Copy link
Copy Markdown
Contributor Author

@bmingles bmingles May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mofojed The reason we need waitForActivation is due to the difference of plugin themes vs external themes.

  • plugin themes - get loaded after login, so we have to be able to render children to show the login form while they are still null.
  • external themes - don't require the login, so we could do the simple themes != null && <ThemeProvider> you suggested. Plugin themes can't do this

I think you are right in that we don't have to pass it down, but as far as I can tell, we do need some mechanism to differentiate.

I'll update accordingly and add more comments, unless you are seeing a better way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, looks like we do need waitForActivation in ThemeProvider since the condition can't be determined from the passed in themes prop. I've added a more detailed comment to the waitForActivation prop.

Comment thread packages/app-utils/src/components/ThemeBootstrap.tsx
Comment thread packages/components/src/theme/ThemeProvider.tsx
Comment on lines +38 to +85
useEffect(() => {
if (!result.isEnabled) {
return;
}

logger.debug('Requesting external theme data');

/** Parse external theme data and update the result */
function handleExternalThemeData(externalThemeData: ExternalThemeData) {
const themeData = parseExternalThemeData(externalThemeData);

setResult({
isEnabled: true,
isPending: false,
themeData,
});
}

/** Parent or current Window can explicitly set the theme */
function onMessage(event: MessageEvent<PostMessage<unknown>>): void {
const parent = getWindowParent();

// Allow messages from parent or current window
if (event.source !== window && event.source !== parent) {
return;
}

if (event.data.message === MSG_REQUEST_SET_THEME) {
if (isExternalThemeData(event.data.payload)) {
handleExternalThemeData(event.data.payload);
}
}
}

window.addEventListener('message', onMessage);

/** Request initial theme data from parent window */
requestExternalThemeData()
.then(handleExternalThemeData)
.catch(err => {
logger.error(err);
setResult({ isEnabled: true, isPending: false });
});

return () => {
window.removeEventListener('message', onMessage);
};
}, [result.isEnabled]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably refactor this effect to make it more concise... the handleExternalThemeData doesn't need to be within it...

const handleExternalThemeData = useCallback((externalThemeData: ExternalThemeData) => ...);

useEffect(function startListeningWindowMessages() {
   function onMessage(...) { ... }
  window.addEventListener('message', onMessage);
  return () => { window.removeEventListener( .... );
}, [handleExternalThemeData]);

useEffect(function initializeExternalTheme() {
  ...
  request.ExternalThemeData().then(handleExternalThemeData).catch(...)
}, [handleExternalThemeData]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines -42 to -55
export interface Response<T = unknown> {
id: string;
payload: T;
}

export function isMessage(obj: unknown): obj is Message {
const message = obj as Message;
return (
message != null &&
typeof message.id === 'string' &&
typeof message.message === 'string'
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing all this stuff is a breaking change. Deprecate it instead (and re-export from utils where possible).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@bmingles bmingles requested a review from mofojed May 9, 2025 15:21
@bmingles bmingles merged commit 42a74ec into deephaven:main May 9, 2025
11 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants