diff --git a/.changeset/warm-mice-repair.md b/.changeset/warm-mice-repair.md new file mode 100644 index 000000000..3814ec3d1 --- /dev/null +++ b/.changeset/warm-mice-repair.md @@ -0,0 +1,5 @@ +--- +'@myst-theme/jupyter': patch +--- + +Separate out launch binder logic into a reusable hook diff --git a/packages/jupyter/src/controls/Buttons.tsx b/packages/jupyter/src/controls/Buttons.tsx index 36d8bf53f..08491351b 100644 --- a/packages/jupyter/src/controls/Buttons.tsx +++ b/packages/jupyter/src/controls/Buttons.tsx @@ -11,8 +11,7 @@ import { import { BoltIcon as BoltIconSolid } from '@heroicons/react/24/solid'; import classNames from 'classnames'; import { Spinner } from './Spinner.js'; -import { useThebeServer } from 'thebe-react'; -import { useCallback, useState } from 'react'; +import { useLaunchBinder } from '../hooks.js'; function BinderButton({ icon, @@ -56,19 +55,8 @@ function BinderButton({ } export function LaunchBinder({ style, location }: { style: 'link' | 'button'; location?: string }) { - const { connect, connecting, ready, server, error } = useThebeServer(); - const [autoOpen, setAutoOpen] = useState(false); - - // automatically click the link when the server is ready - // but only if the connection was initiated in this component by the user - const autoClick = useCallback( - (node: HTMLAnchorElement) => { - if (node != null && autoOpen) { - node.click(); - } - }, - [autoOpen], - ); + const { connecting, ready, error, autoClickRef, handleStart, getUserServerUrl } = + useLaunchBinder(); let btnStyles = 'flex gap-1 px-2 py-1 font-normal no-underline border rounded bg-slate-200 border-slate-600 hover:bg-slate-800 hover:text-white hover:border-transparent'; @@ -85,29 +73,13 @@ export function LaunchBinder({ style, location }: { style: 'link' | 'button'; lo 'inline-flex items-center mr-2 font-medium no-underline text-gray-900 lg:mr-0 lg:flex'; } - const handleStart = () => { - if (!connect) { - console.debug("LaunchBinder: Trying to start a connection but connect() isn't defined"); - return; - } - setAutoOpen(true); - connect(); - }; - if (ready) { // we expect ?token= to be in the url - let userServerUrl = server?.userServerUrl; - if (userServerUrl && location) { - // add the location to the url pathname - const url = new URL(userServerUrl); - if (url.pathname.endsWith('/')) url.pathname = url.pathname.slice(0, -1); - url.pathname = `${url.pathname}/lab/tree${location}`; - userServerUrl = url.toString(); - } + const userServerUrl = getUserServerUrl(location); return ( { + if (node != null && autoOpen) { + node.click(); + } + }, + [autoOpen], + ); + + const handleStart = useCallback(() => { + if (!connect) { + console.debug("LaunchBinder: Trying to start a connection but connect() isn't defined"); + return; + } + setAutoOpen(true); + connect(); + }, [connect]); + + const getUserServerUrl = useCallback( + (location?: string) => { + let userServerUrl = server?.userServerUrl; + if (userServerUrl && location) { + // add the location to the url pathname + const url = new URL(userServerUrl); + if (url.pathname.endsWith('/')) url.pathname = url.pathname.slice(0, -1); + url.pathname = `${url.pathname}/lab/tree${location}`; + userServerUrl = url.toString(); + } + return userServerUrl; + }, + [server], + ); + + return { + connecting, + ready, + error, + autoClickRef, + handleStart, + getUserServerUrl, + }; +} diff --git a/packages/jupyter/src/index.tsx b/packages/jupyter/src/index.tsx index 92cb2ea6b..9a6a6857a 100644 --- a/packages/jupyter/src/index.tsx +++ b/packages/jupyter/src/index.tsx @@ -14,5 +14,7 @@ export * from './ConnectionStatusTray.js'; export * from './providers.js'; export * from './execute/index.js'; export * from './controls/index.js'; +export * from './utils.js'; +export { useLaunchBinder } from './hooks.js'; export default OUTPUT_RENDERERS; diff --git a/packages/jupyter/src/providers.tsx b/packages/jupyter/src/providers.tsx index 889e291b5..a5e78706c 100644 --- a/packages/jupyter/src/providers.tsx +++ b/packages/jupyter/src/providers.tsx @@ -8,7 +8,7 @@ import type { RepoProviderSpec } from 'thebe-core'; function makeThebeOptions( siteManifest: SiteManifest | undefined, - optionsOverrideFn = (opts?: ExtendedCoreOptions) => opts, + optionsOverrideFn = (opts: ExtendedCoreOptions) => opts, ): { options?: ExtendedCoreOptions; githubBadgeUrl?: string; @@ -27,7 +27,8 @@ function makeThebeOptions( binderBadgeUrl, ); - const options = optionsOverrideFn(thebeFrontmatter ? optionsFromFrontmatter : undefined); + let options = optionsFromFrontmatter; + if (options) options = optionsOverrideFn(options); return { options, @@ -51,7 +52,7 @@ export function ConfiguredThebeServerProvider({ children, }: React.PropsWithChildren<{ siteManifest?: SiteManifest; - optionOverrideFn?: (opts?: ExtendedCoreOptions) => ExtendedCoreOptions; + optionOverrideFn?: (opts: ExtendedCoreOptions) => ExtendedCoreOptions; customRepoProviders?: RepoProviderSpec[]; }>) { const thebe = React.useMemo(